@stackone/mcp-connectors
Version:
MCP connectors for disco.dev
1,215 lines (1,211 loc) • 585 kB
JavaScript
import { mcpConnectorConfig } from "@stackone/mcp-config-types";
import { z } from "zod";
import OpenAI from "openai";
import { create, insertMultiple, search } from "@orama/orama";
import { parse } from "node-html-parser";
import { LinearClient } from "@linear/sdk";
import { ItemBuilder, OnePasswordConnect } from "@1password/connect";
//#region src/connectors/asana.ts
var AsanaClient = class {
headers;
baseUrl = "https://app.asana.com/api/1.0";
constructor(accessToken) {
this.headers = {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
};
}
async getUser(userGid = "me") {
const response = await fetch(`${this.baseUrl}/users/${userGid}`, { headers: this.headers });
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async getWorkspaces() {
const response = await fetch(`${this.baseUrl}/workspaces`, { headers: this.headers });
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async getTeams(workspaceGid) {
const response = await fetch(`${this.baseUrl}/workspaces/${workspaceGid}/teams`, { headers: this.headers });
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async getProjects(workspaceGid, teamGid, limit = 50) {
const params = new URLSearchParams({ limit: limit.toString() });
if (workspaceGid) params.append("workspace", workspaceGid);
if (teamGid) params.append("team", teamGid);
const response = await fetch(`${this.baseUrl}/projects?${params}`, { headers: this.headers });
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async getProject(projectGid) {
const response = await fetch(`${this.baseUrl}/projects/${projectGid}`, { headers: this.headers });
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async getTasks(projectGid, assigneeGid, workspaceGid, completedSince, limit = 50) {
const params = new URLSearchParams({
limit: limit.toString(),
opt_fields: "name,notes,completed,assignee,assignee.name,due_at,due_on,projects,projects.name,tags,tags.name,custom_fields,created_at,modified_at,permalink_url"
});
if (projectGid) params.append("project", projectGid);
if (assigneeGid) params.append("assignee", assigneeGid);
if (workspaceGid) params.append("workspace", workspaceGid);
if (completedSince) params.append("completed_since", completedSince);
const response = await fetch(`${this.baseUrl}/tasks?${params}`, { headers: this.headers });
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async getTask(taskGid) {
const params = new URLSearchParams({ opt_fields: "name,notes,html_notes,completed,assignee,assignee.name,assignee_status,due_at,due_on,projects,projects.name,memberships,memberships.project,memberships.project.name,memberships.section,memberships.section.name,tags,tags.name,followers,followers.name,custom_fields,custom_fields.name,custom_fields.type,custom_fields.number_value,custom_fields.text_value,custom_fields.enum_value,custom_fields.enum_value.name,created_at,modified_at,permalink_url" });
const response = await fetch(`${this.baseUrl}/tasks/${taskGid}?${params}`, { headers: this.headers });
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async createTask(data) {
const response = await fetch(`${this.baseUrl}/tasks`, {
method: "POST",
headers: {
...this.headers,
"Content-Type": "application/json"
},
body: JSON.stringify({ data })
});
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async updateTask(taskGid, data) {
const response = await fetch(`${this.baseUrl}/tasks/${taskGid}`, {
method: "PUT",
headers: {
...this.headers,
"Content-Type": "application/json"
},
body: JSON.stringify({ data })
});
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async deleteTask(taskGid) {
const response = await fetch(`${this.baseUrl}/tasks/${taskGid}`, {
method: "DELETE",
headers: this.headers
});
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async createProject(data) {
const response = await fetch(`${this.baseUrl}/projects`, {
method: "POST",
headers: {
...this.headers,
"Content-Type": "application/json"
},
body: JSON.stringify({ data })
});
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async updateProject(projectGid, data) {
const response = await fetch(`${this.baseUrl}/projects/${projectGid}`, {
method: "PUT",
headers: {
...this.headers,
"Content-Type": "application/json"
},
body: JSON.stringify({ data })
});
if (!response.ok) throw new Error(`Asana API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
};
const AsanaConnectorConfig = mcpConnectorConfig({
name: "Asana",
key: "asana",
version: "1.0.0",
logo: "https://stackone-logos.com/api/asana/filled/svg",
credentials: z.object({ accessToken: z.string().describe("Asana Personal Access Token from Settings > Apps > Personal Access Tokens :: 0/68a9e79b868c6789e79a124c30b0") }),
setup: z.object({}),
examplePrompt: "Create a new task called \"Review quarterly reports\" assigned to me with a due date of next Friday, and list all projects in our main workspace.",
tools: (tool) => ({
GET_USER: tool({
name: "asana_get_user",
description: "Get information about the current user or a specific user",
schema: z.object({ userGid: z.string().optional().describe("User GID to get info for (omit for current user)") }),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const user = await new AsanaClient(accessToken).getUser(args.userGid);
return JSON.stringify(user, null, 2);
} catch (error) {
return `Failed to get user: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
LIST_WORKSPACES: tool({
name: "asana_list_workspaces",
description: "List all workspaces the user has access to",
schema: z.object({}),
handler: async (_args, context) => {
try {
const { accessToken } = await context.getCredentials();
const workspaces = await new AsanaClient(accessToken).getWorkspaces();
return JSON.stringify(workspaces, null, 2);
} catch (error) {
return `Failed to list workspaces: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
LIST_TEAMS: tool({
name: "asana_list_teams",
description: "List teams in a workspace",
schema: z.object({ workspaceGid: z.string().describe("Workspace GID to list teams for") }),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const teams = await new AsanaClient(accessToken).getTeams(args.workspaceGid);
return JSON.stringify(teams, null, 2);
} catch (error) {
return `Failed to list teams: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
LIST_PROJECTS: tool({
name: "asana_list_projects",
description: "List projects with optional filtering",
schema: z.object({
workspaceGid: z.string().optional().describe("Workspace GID to filter projects"),
teamGid: z.string().optional().describe("Team GID to filter projects"),
limit: z.number().default(50).describe("Maximum number of projects to return")
}),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const projects = await new AsanaClient(accessToken).getProjects(args.workspaceGid, args.teamGid, args.limit);
return JSON.stringify(projects, null, 2);
} catch (error) {
return `Failed to list projects: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_PROJECT: tool({
name: "asana_get_project",
description: "Get detailed information about a specific project",
schema: z.object({ projectGid: z.string().describe("Project GID to retrieve") }),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const project = await new AsanaClient(accessToken).getProject(args.projectGid);
return JSON.stringify(project, null, 2);
} catch (error) {
return `Failed to get project: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
LIST_TASKS: tool({
name: "asana_list_tasks",
description: "List tasks with optional filtering",
schema: z.object({
projectGid: z.string().optional().describe("Project GID to filter tasks"),
assigneeGid: z.string().optional().describe("Assignee GID to filter tasks"),
workspaceGid: z.string().optional().describe("Workspace GID to filter tasks"),
completedSince: z.string().optional().describe("ISO 8601 datetime to filter completed tasks since"),
limit: z.number().default(50).describe("Maximum number of tasks to return")
}),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const tasks = await new AsanaClient(accessToken).getTasks(args.projectGid, args.assigneeGid, args.workspaceGid, args.completedSince, args.limit);
return JSON.stringify(tasks, null, 2);
} catch (error) {
return `Failed to list tasks: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_TASK: tool({
name: "asana_get_task",
description: "Get detailed information about a specific task",
schema: z.object({ taskGid: z.string().describe("Task GID to retrieve") }),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const task = await new AsanaClient(accessToken).getTask(args.taskGid);
return JSON.stringify(task, null, 2);
} catch (error) {
return `Failed to get task: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
CREATE_TASK: tool({
name: "asana_create_task",
description: "Create a new task",
schema: z.object({
name: z.string().describe("Task name"),
notes: z.string().optional().describe("Task notes (plain text)"),
htmlNotes: z.string().optional().describe("Task notes (HTML format)"),
assignee: z.string().optional().describe("Assignee GID or \"me\""),
projects: z.array(z.string()).optional().describe("Array of project GIDs"),
dueAt: z.string().optional().describe("Due date and time (ISO 8601)"),
dueOn: z.string().optional().describe("Due date (YYYY-MM-DD)"),
completed: z.boolean().optional().describe("Whether the task is completed")
}),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const task = await new AsanaClient(accessToken).createTask({
name: args.name,
notes: args.notes,
html_notes: args.htmlNotes,
assignee: args.assignee,
projects: args.projects,
due_at: args.dueAt,
due_on: args.dueOn,
completed: args.completed
});
return JSON.stringify(task, null, 2);
} catch (error) {
return `Failed to create task: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
UPDATE_TASK: tool({
name: "asana_update_task",
description: "Update an existing task",
schema: z.object({
taskGid: z.string().describe("Task GID to update"),
name: z.string().optional().describe("Task name"),
notes: z.string().optional().describe("Task notes (plain text)"),
htmlNotes: z.string().optional().describe("Task notes (HTML format)"),
assignee: z.string().optional().describe("Assignee GID or \"me\""),
dueAt: z.string().optional().describe("Due date and time (ISO 8601)"),
dueOn: z.string().optional().describe("Due date (YYYY-MM-DD)"),
completed: z.boolean().optional().describe("Whether the task is completed")
}),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const task = await new AsanaClient(accessToken).updateTask(args.taskGid, {
name: args.name,
notes: args.notes,
html_notes: args.htmlNotes,
assignee: args.assignee,
due_at: args.dueAt,
due_on: args.dueOn,
completed: args.completed
});
return JSON.stringify(task, null, 2);
} catch (error) {
return `Failed to update task: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
DELETE_TASK: tool({
name: "asana_delete_task",
description: "Delete a task",
schema: z.object({ taskGid: z.string().describe("Task GID to delete") }),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const result = await new AsanaClient(accessToken).deleteTask(args.taskGid);
return JSON.stringify(result, null, 2);
} catch (error) {
return `Failed to delete task: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
CREATE_PROJECT: tool({
name: "asana_create_project",
description: "Create a new project",
schema: z.object({
name: z.string().describe("Project name"),
notes: z.string().optional().describe("Project notes"),
team: z.string().optional().describe("Team GID"),
workspace: z.string().optional().describe("Workspace GID"),
public: z.boolean().optional().describe("Whether the project is public"),
color: z.string().optional().describe("Project color")
}),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const project = await new AsanaClient(accessToken).createProject({
name: args.name,
notes: args.notes,
team: args.team,
workspace: args.workspace,
public: args.public,
color: args.color
});
return JSON.stringify(project, null, 2);
} catch (error) {
return `Failed to create project: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
UPDATE_PROJECT: tool({
name: "asana_update_project",
description: "Update an existing project",
schema: z.object({
projectGid: z.string().describe("Project GID to update"),
name: z.string().optional().describe("Project name"),
notes: z.string().optional().describe("Project notes"),
color: z.string().optional().describe("Project color"),
public: z.boolean().optional().describe("Whether the project is public"),
archived: z.boolean().optional().describe("Whether the project is archived")
}),
handler: async (args, context) => {
try {
const { accessToken } = await context.getCredentials();
const project = await new AsanaClient(accessToken).updateProject(args.projectGid, {
name: args.name,
notes: args.notes,
color: args.color,
public: args.public,
archived: args.archived
});
return JSON.stringify(project, null, 2);
} catch (error) {
return `Failed to update project: ${error instanceof Error ? error.message : String(error)}`;
}
}
})
})
});
//#endregion
//#region src/connectors/attio.ts
var AttioClient = class {
headers;
baseUrl = "https://api.attio.com/v2";
constructor(apiKey) {
this.headers = {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
"Content-Type": "application/json"
};
}
async getFilteredLists(keyword = "customer_success") {
const response = await fetch(`${this.baseUrl}/lists`, { headers: this.headers });
if (!response.ok) throw new Error(`Attio API error: ${response.status} ${response.statusText}`);
return ((await response.json()).data || []).filter((list) => list.name.toLowerCase().includes(keyword.toLowerCase()));
}
async getListDetails(listSlug) {
const response = await fetch(`${this.baseUrl}/lists/${listSlug}`, { headers: this.headers });
if (!response.ok) throw new Error(`Attio API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async getListEntries(listSlug, limit = 50) {
const body = { limit };
const response = await fetch(`${this.baseUrl}/lists/${listSlug}/entries/query`, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body)
});
if (!response.ok) throw new Error(`Attio API error: ${response.status} ${response.statusText}`);
return (await response.json()).data || [];
}
};
const AttioConnectorConfig = mcpConnectorConfig({
name: "Attio",
key: "attio",
logo: "https://stackone-logos.com/api/attio/filled/svg",
version: "2.0.0",
credentials: z.object({ apiKey: z.string().describe("Attio API Key from Workspace settings > Developers tab > New access token. :: sk_live_1234567890abcdefghijklmnopqrstuvwxyz :: https://attio.com/help/apps/other-apps/generating-an-api-key") }),
setup: z.object({}),
examplePrompt: "Get all my lists on Attio",
tools: (tool) => ({
GET_LIST: tool({
name: "attio_get_list",
description: "Get lists filtered by keyword to avoid context overflow. Defaults to customer_success list.",
schema: z.object({ keyword: z.string().default("customer_success").describe("Keyword to filter lists by (e.g., \"customer_success\", \"sales\")") }),
handler: async (args, context) => {
try {
const { apiKey } = await context.getCredentials();
const lists = await new AttioClient(apiKey).getFilteredLists(args.keyword);
if (lists.length === 0) return `No lists found matching keyword: "${args.keyword}"`;
const listSummary = lists.map((list) => ({
id: list.id,
name: list.name,
parent_object: list.parent_object,
created_at: list.created_at
}));
return JSON.stringify({
keyword_used: args.keyword,
total_matches: lists.length,
lists: listSummary
}, null, 2);
} catch (error) {
return `Failed to get filtered lists: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_LIST_FIELDS: tool({
name: "attio_get_list_fields",
description: "Get all fields or attributes within a specific list",
schema: z.object({ listSlug: z.string().describe("List slug or ID (e.g., \"customer_success_list\")") }),
handler: async (args, context) => {
try {
const { apiKey } = await context.getCredentials();
const listDetails = await new AttioClient(apiKey).getListDetails(args.listSlug);
const fieldInfo = {
list_id: listDetails.id,
list_name: listDetails.name,
parent_object: listDetails.parent_object,
attributes: listDetails.attributes || [],
created_at: listDetails.created_at
};
return JSON.stringify(fieldInfo, null, 2);
} catch (error) {
return `Failed to get list fields: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_LIST_ENTRIES: tool({
name: "attio_get_list_entries",
description: "Get all entries found within a specific list, with limited results to avoid context overflow",
schema: z.object({
listSlug: z.string().describe("List slug or ID"),
limit: z.number().default(50).describe("Maximum number of entries to return (default 50 to avoid context overflow)")
}),
handler: async (args, context) => {
try {
const { apiKey } = await context.getCredentials();
const entries = await new AttioClient(apiKey).getListEntries(args.listSlug, args.limit);
if (entries.length === 0) return `No entries found in list: "${args.listSlug}"`;
const entrySummary = entries.map((entry) => ({
id: entry.id,
parent_record_id: entry.parent_record_id,
created_at: entry.created_at,
values: entry.values
}));
return JSON.stringify({
list_slug: args.listSlug,
total_entries: entries.length,
limit_applied: args.limit,
entries: entrySummary
}, null, 2);
} catch (error) {
return `Failed to get list entries: ${error instanceof Error ? error.message : String(error)}`;
}
}
})
})
});
//#endregion
//#region src/connectors/aws.ts
var AwsClient = class {
region;
credentials;
constructor(credentials) {
this.credentials = credentials;
this.region = credentials.region;
}
async makeAwsRequest(service, action, params = {}, method = "POST") {
const maxRetries = 3;
const baseDelay = 1e3;
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
return await this.executeAwsRequest(service, action, params, method);
} catch (error) {
if (attempt === maxRetries || !this.isRetryableError(error)) throw error;
const delay = baseDelay * 2 ** attempt + Math.random() * 1e3;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
isRetryableError(error) {
if (error instanceof Error && (error.name === "NetworkError" || error.name === "TimeoutError")) return true;
if (error instanceof Error && "status" in error && typeof error.status === "number") return error.status >= 500 || error.status === 429;
return false;
}
async executeAwsRequest(service, action, params = {}, method = "POST") {
const endpoint = this.getServiceEndpoint(service);
const headers = await this.getServiceHeaders(service, action);
const body = this.getRequestBody(service, params, method);
const signedRequest = await this.signRequest(method, endpoint, headers, body, service);
const response = await fetch(signedRequest.url, {
method,
headers: signedRequest.headers,
body
});
if (!response.ok) {
const errorText = await response.text();
throw await this.handleAwsError(response, errorText, service);
}
const contentType = response.headers.get("content-type") || "";
if (contentType.includes("application/json")) return response.json();
if (contentType.includes("text/xml") || contentType.includes("application/xml")) return this.parseXmlResponse(await response.text());
return response.text();
}
getServiceEndpoint(service) {
return {
ec2: `https://ec2.${this.region}.amazonaws.com`,
s3: `https://s3.${this.region}.amazonaws.com`,
lambda: `https://lambda.${this.region}.amazonaws.com`,
monitoring: `https://monitoring.${this.region}.amazonaws.com`,
ce: "https://ce.us-east-1.amazonaws.com"
}[service] || `https://${service}.${this.region}.amazonaws.com`;
}
getServiceHeaders(service, action) {
const headers = {
Host: `${service}.${this.region}.amazonaws.com`,
"X-Amz-Date": (/* @__PURE__ */ new Date()).toISOString().replace(/[:\-]|\.\d{3}/g, "")
};
if (this.credentials.sessionToken) headers["X-Amz-Security-Token"] = this.credentials.sessionToken;
switch (service) {
case "ec2":
headers["Content-Type"] = "application/x-www-form-urlencoded";
break;
case "lambda":
headers["Content-Type"] = "application/x-amz-json-1.0";
break;
case "monitoring":
headers["Content-Type"] = "application/x-www-form-urlencoded";
break;
case "ce":
headers["Content-Type"] = "application/x-amz-json-1.1";
headers["X-Amz-Target"] = `AWSInsightsIndexService.${action}`;
break;
case "s3":
headers["Content-Type"] = "application/xml";
break;
default:
headers["Content-Type"] = "application/x-amz-json-1.1";
headers["X-Amz-Target"] = `${service}.${action}`;
}
return headers;
}
getRequestBody(service, params, method) {
if (method === "GET") return;
switch (service) {
case "ec2":
case "monitoring": return this.encodeFormData(params);
case "s3": return params.body || "";
default: return JSON.stringify(params);
}
}
encodeFormData(params) {
const formData = new URLSearchParams();
for (const [key, value] of Object.entries(params)) if (Array.isArray(value)) value.forEach((item, index) => {
formData.append(`${key}.${index + 1}`, String(item));
});
else formData.append(key, String(value));
return formData.toString();
}
async signRequest(method, endpoint, headers, body, service) {
const url = new URL(endpoint);
const path = url.pathname;
const queryString = url.search.slice(1);
const timestamp = headers["X-Amz-Date"];
if (!timestamp) throw new Error("X-Amz-Date header is required");
const date = timestamp.slice(0, 8);
const canonicalHeaders = Object.entries(headers).sort(([a], [b]) => a.toLowerCase().localeCompare(b.toLowerCase())).map(([key, value]) => `${key.toLowerCase()}:${value.trim()}`).join("\n");
const signedHeaders = Object.keys(headers).map((key) => key.toLowerCase()).sort().join(";");
const payloadHash = await this.sha256(body || "");
const canonicalRequest = [
method,
path,
queryString,
canonicalHeaders,
"",
signedHeaders,
payloadHash
].join("\n");
const algorithm = "AWS4-HMAC-SHA256";
const credentialScope = `${date}/${this.region}/${service}/aws4_request`;
const stringToSign = [
algorithm,
timestamp,
credentialScope,
await this.sha256(canonicalRequest)
].join("\n");
const signature = await this.calculateSignature(stringToSign, date, service);
const authorizationHeader = `${algorithm} Credential=${this.credentials.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
return {
url: endpoint,
headers: {
...headers,
Authorization: authorizationHeader
}
};
}
async calculateSignature(stringToSign, date, service) {
const kDate = await this.hmacSha256(`AWS4${this.credentials.secretAccessKey}`, date);
const kRegion = await this.hmacSha256(kDate, this.region);
const kService = await this.hmacSha256(kRegion, service);
const kSigning = await this.hmacSha256(kService, "aws4_request");
const signature = await this.hmacSha256(kSigning, stringToSign);
return this.toHex(signature);
}
async sha256(data) {
const dataBuffer = new TextEncoder().encode(data);
const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
return this.toHex(hashBuffer);
}
async hmacSha256(key, data) {
const encoder = new TextEncoder();
const keyBuffer = typeof key === "string" ? encoder.encode(key) : key;
const dataBuffer = encoder.encode(data);
const cryptoKey = await crypto.subtle.importKey("raw", keyBuffer, {
name: "HMAC",
hash: "SHA-256"
}, false, ["sign"]);
return crypto.subtle.sign("HMAC", cryptoKey, dataBuffer);
}
toHex(buffer) {
return Array.from(new Uint8Array(buffer)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
}
async handleAwsError(response, errorText, service) {
try {
const contentType = response.headers.get("content-type") || "";
let errorInfo = {};
if (contentType.includes("application/json")) errorInfo = JSON.parse(errorText);
else if (contentType.includes("xml")) errorInfo = this.parseXmlResponse(errorText);
else errorInfo = { message: errorText };
const errorCode = errorInfo.code || errorInfo.Code || errorInfo.__type || "UnknownError";
const errorMessage = errorInfo.message || errorInfo.Message || errorInfo.error || errorText;
const error = /* @__PURE__ */ new Error(`AWS ${service} API error (${response.status}): ${errorCode} - ${errorMessage}`);
error.status = response.status;
error.code = errorCode;
error.service = service;
return error;
} catch (_parseError) {
const error = /* @__PURE__ */ new Error(`AWS ${service} API error (${response.status}): ${response.statusText}`);
error.status = response.status;
error.service = service;
return error;
}
}
parseXmlResponse(xml) {
const parseXmlString = (xmlStr) => {
const obj = {};
const tagRegex = /<([^>\/\s]+)(?:[^>]*)>([^<]*(?:<(?!\1[>\s])[^<]*)*)<\/\1>/g;
const selfClosingRegex = /<([^>\/\s]+)(?:[^>]*)\s*\/>/g;
let match = selfClosingRegex.exec(xmlStr);
while (match !== null) {
const key = match[1];
if (key) obj[key] = "";
match = selfClosingRegex.exec(xmlStr);
}
tagRegex.lastIndex = 0;
match = tagRegex.exec(xmlStr);
while (match !== null) {
const key = match[1];
const rawContent = match[2];
if (key && rawContent !== void 0) {
const content = rawContent.trim();
if (content.includes("<")) {
const nestedValue = parseXmlString(content);
const currentValue = obj[key];
if (currentValue !== void 0) if (Array.isArray(currentValue)) currentValue.push(nestedValue);
else obj[key] = [currentValue, nestedValue];
else obj[key] = nestedValue;
} else {
const currentValue = obj[key];
if (currentValue !== void 0) if (Array.isArray(currentValue)) currentValue.push(content);
else obj[key] = [currentValue, content];
else obj[key] = content;
}
}
match = tagRegex.exec(xmlStr);
}
return obj;
};
return parseXmlString(xml);
}
async listEC2Instances() {
try {
const result = await this.makeAwsRequest("ec2", "DescribeInstances", {
Action: "DescribeInstances",
Version: "2016-11-15"
});
const instances = [];
if (result.reservationSet) {
for (const reservation of result.reservationSet) if (reservation.instancesSet) instances.push(...reservation.instancesSet);
}
return instances;
} catch (error) {
throw new Error(`Failed to list EC2 instances: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getEC2Instance(instanceId) {
try {
const result = await this.makeAwsRequest("ec2", "DescribeInstances", {
Action: "DescribeInstances",
Version: "2016-11-15",
"InstanceId.1": instanceId
});
if (result.reservationSet?.[0]?.instancesSet?.[0]) return result.reservationSet[0].instancesSet[0];
throw new Error("Instance not found");
} catch (error) {
throw new Error(`Failed to get EC2 instance: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listS3Buckets() {
try {
return (await this.makeAwsRequest("s3", "ListBuckets", {}, "GET")).ListAllMyBucketsResult?.Buckets?.Bucket || [];
} catch (error) {
throw new Error(`Failed to list S3 buckets: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listS3Objects(bucketName, prefix, maxKeys = 100) {
try {
const endpoint = `https://${bucketName}.s3.${this.region}.amazonaws.com`;
const queryParams = new URLSearchParams();
queryParams.append("list-type", "2");
queryParams.append("max-keys", maxKeys.toString());
if (prefix) queryParams.append("prefix", prefix);
const url = `${endpoint}?${queryParams.toString()}`;
const headers = await this.getServiceHeaders("s3", "ListObjectsV2");
const signedRequest = await this.signRequest("GET", url, headers, void 0, "s3");
const response = await fetch(signedRequest.url, {
method: "GET",
headers: signedRequest.headers
});
if (!response.ok) {
const errorText = await response.text();
throw await this.handleAwsError(response, errorText, "s3");
}
const xmlText = await response.text();
return this.parseXmlResponse(xmlText).ListBucketResult?.Contents || [];
} catch (error) {
throw new Error(`Failed to list S3 objects: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listLambdaFunctions() {
try {
return (await this.makeAwsRequest("lambda", "ListFunctions", {})).Functions || [];
} catch (error) {
throw new Error(`Failed to list Lambda functions: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getLambdaFunction(functionName) {
try {
const endpoint = `https://lambda.${this.region}.amazonaws.com/2015-03-31/functions/${encodeURIComponent(functionName)}`;
const headers = await this.getServiceHeaders("lambda", "GetFunction");
const signedRequest = await this.signRequest("GET", endpoint, headers, void 0, "lambda");
const response = await fetch(signedRequest.url, {
method: "GET",
headers: signedRequest.headers
});
if (!response.ok) {
const errorText = await response.text();
throw await this.handleAwsError(response, errorText, "lambda");
}
return (await response.json()).Configuration;
} catch (error) {
throw new Error(`Failed to get Lambda function: ${error instanceof Error ? error.message : String(error)}`);
}
}
async invokeLambdaFunction(functionName, payload) {
try {
const endpoint = `https://lambda.${this.region}.amazonaws.com/2015-03-31/functions/${encodeURIComponent(functionName)}/invocations`;
const body = payload ? JSON.stringify(payload) : "";
const headers = await this.getServiceHeaders("lambda", "Invoke");
headers["X-Amz-Invocation-Type"] = "RequestResponse";
const signedRequest = await this.signRequest("POST", endpoint, headers, body, "lambda");
const response = await fetch(signedRequest.url, {
method: "POST",
headers: signedRequest.headers,
body
});
if (!response.ok) {
const errorText = await response.text();
throw await this.handleAwsError(response, errorText, "lambda");
}
return response.json();
} catch (error) {
throw new Error(`Failed to invoke Lambda function: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getCloudWatchMetrics(namespace, metricName, dimensions, startTime, endTime) {
try {
const params = {
Action: "GetMetricStatistics",
Version: "2010-08-01",
Namespace: namespace,
MetricName: metricName,
StartTime: startTime || (/* @__PURE__ */ new Date(Date.now() - 1440 * 60 * 1e3)).toISOString(),
EndTime: endTime || (/* @__PURE__ */ new Date()).toISOString(),
Period: 300,
"Statistics.member.1": "Average",
"Statistics.member.2": "Maximum",
"Statistics.member.3": "Sum"
};
if (dimensions) dimensions.forEach((dimension, index) => {
params[`Dimensions.member.${index + 1}.Name`] = dimension.Name;
params[`Dimensions.member.${index + 1}.Value`] = dimension.Value;
});
return (await this.makeAwsRequest("monitoring", "GetMetricStatistics", params)).GetMetricStatisticsResult?.Datapoints?.member || [];
} catch (error) {
throw new Error(`Failed to get CloudWatch metrics: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getCostAndUsage(startTime, endTime, granularity = "DAILY") {
try {
const params = {
TimePeriod: {
Start: startTime || (/* @__PURE__ */ new Date(Date.now() - 720 * 60 * 60 * 1e3)).toISOString().split("T")[0],
End: endTime || (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
},
Granularity: granularity,
Metrics: ["BlendedCost", "UsageQuantity"],
GroupBy: [{
Type: "DIMENSION",
Key: "SERVICE"
}]
};
return (await this.makeAwsRequest("ce", "GetCostAndUsage", params)).ResultsByTime || [];
} catch (error) {
throw new Error(`Failed to get cost and usage: ${error instanceof Error ? error.message : String(error)}`);
}
}
};
const AwsConnectorConfig = mcpConnectorConfig({
name: "AWS",
key: "aws",
version: "1.0.0",
logo: "https://stackone-logos.com/api/amazon-redshift/filled/svg",
credentials: z.object({
accessKeyId: z.string().describe("AWS Access Key ID :: AKIAIOSFODNN7EXAMPLE"),
secretAccessKey: z.string().describe("AWS Secret Access Key :: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
sessionToken: z.string().optional().describe("AWS Session Token (for temporary credentials) :: AQoDYXdzEJr...<remainder of session token>")
}),
setup: z.object({ region: z.string().default("us-east-1").describe("AWS region (e.g., us-east-1, eu-west-1) :: us-east-1") }),
examplePrompt: "List all my EC2 instances in the us-east-1 region, check my S3 buckets, and show me the cost breakdown for the last 30 days.",
tools: (tool) => ({
LIST_EC2_INSTANCES: tool({
name: "aws_list_ec2_instances",
description: "List all EC2 instances in the specified region",
schema: z.object({}),
handler: async (_args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const instances = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).listEC2Instances();
return JSON.stringify(instances, null, 2);
} catch (error) {
return `Failed to list EC2 instances: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_EC2_INSTANCE: tool({
name: "aws_get_ec2_instance",
description: "Get details of a specific EC2 instance",
schema: z.object({ instanceId: z.string().describe("The EC2 instance ID") }),
handler: async (args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const instance = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).getEC2Instance(args.instanceId);
return JSON.stringify(instance, null, 2);
} catch (error) {
return `Failed to get EC2 instance: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
LIST_S3_BUCKETS: tool({
name: "aws_list_s3_buckets",
description: "List all S3 buckets in the account",
schema: z.object({}),
handler: async (_args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const buckets = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).listS3Buckets();
return JSON.stringify(buckets, null, 2);
} catch (error) {
return `Failed to list S3 buckets: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
LIST_S3_OBJECTS: tool({
name: "aws_list_s3_objects",
description: "List objects in an S3 bucket",
schema: z.object({
bucketName: z.string().describe("The S3 bucket name"),
prefix: z.string().optional().describe("Filter objects by prefix"),
maxKeys: z.number().default(100).describe("Maximum number of objects to return")
}),
handler: async (args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const objects = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).listS3Objects(args.bucketName, args.prefix, args.maxKeys);
return JSON.stringify(objects, null, 2);
} catch (error) {
return `Failed to list S3 objects: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
LIST_LAMBDA_FUNCTIONS: tool({
name: "aws_list_lambda_functions",
description: "List all Lambda functions in the specified region",
schema: z.object({}),
handler: async (_args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const functions = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).listLambdaFunctions();
return JSON.stringify(functions, null, 2);
} catch (error) {
return `Failed to list Lambda functions: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_LAMBDA_FUNCTION: tool({
name: "aws_get_lambda_function",
description: "Get details of a specific Lambda function",
schema: z.object({ functionName: z.string().describe("The Lambda function name or ARN") }),
handler: async (args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const func = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).getLambdaFunction(args.functionName);
return JSON.stringify(func, null, 2);
} catch (error) {
return `Failed to get Lambda function: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
INVOKE_LAMBDA_FUNCTION: tool({
name: "aws_invoke_lambda_function",
description: "Invoke a Lambda function with optional payload",
schema: z.object({
functionName: z.string().describe("The Lambda function name or ARN"),
payload: z.record(z.any()).optional().describe("JSON payload to send to the function")
}),
handler: async (args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const result = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).invokeLambdaFunction(args.functionName, args.payload);
return JSON.stringify(result, null, 2);
} catch (error) {
return `Failed to invoke Lambda function: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_CLOUDWATCH_METRICS: tool({
name: "aws_get_cloudwatch_metrics",
description: "Get CloudWatch metrics for monitoring",
schema: z.object({
namespace: z.string().describe("CloudWatch namespace (e.g., AWS/EC2, AWS/Lambda)"),
metricName: z.string().describe("Name of the metric to retrieve"),
dimensions: z.array(z.object({
Name: z.string(),
Value: z.string()
})).optional().describe("Metric dimensions for filtering"),
startTime: z.string().optional().describe("Start time (ISO 8601 format)"),
endTime: z.string().optional().describe("End time (ISO 8601 format)")
}),
handler: async (args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const metrics = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).getCloudWatchMetrics(args.namespace, args.metricName, args.dimensions, args.startTime, args.endTime);
return JSON.stringify(metrics, null, 2);
} catch (error) {
return `Failed to get CloudWatch metrics: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_COST_AND_USAGE: tool({
name: "aws_get_cost_and_usage",
description: "Get AWS cost and usage information",
schema: z.object({
startTime: z.string().optional().describe("Start date (YYYY-MM-DD format)"),
endTime: z.string().optional().describe("End date (YYYY-MM-DD format)"),
granularity: z.enum([
"DAILY",
"MONTHLY",
"HOURLY"
]).default("DAILY").describe("Time granularity")
}),
handler: async (args, context) => {
try {
const { accessKeyId, secretAccessKey, sessionToken } = await context.getCredentials();
const { region } = await context.getSetup();
const costData = await new AwsClient({
accessKeyId,
secretAccessKey,
region,
sessionToken
}).getCostAndUsage(args.startTime, args.endTime, args.granularity);
return JSON.stringify(costData, null, 2);
} catch (error) {
return `Failed to get cost and usage: ${error instanceof Error ? error.message : String(error)}`;
}
}
})
})
});
//#endregion
//#region src/connectors/datadog.ts
var DatadogClient = class {
headers;
baseUrl;
constructor(apiKey, appKey, site = "datadoghq.com") {
this.headers = {
"DD-API-KEY": apiKey,
"DD-APPLICATION-KEY": appKey,
"Content-Type": "application/json"
};
this.baseUrl = `https://api.${site}`;
}
async listIncidents(limit = 30, state, severity) {
const params = new URLSearchParams({ "page[size]": limit.toString() });
if (state) params.append("filter[state]", state);
if (severity) params.append("filter[severity]", severity);
const response = await fetch(`${this.baseUrl}/api/v2/incidents?${params}`, { headers: this.headers });
if (!response.ok) throw new Error(`Datadog API error: ${response.status} ${response.statusText}`);
return (await response.json()).data || [];
}
async getIncident(incidentId) {
const response = await fetch(`${this.baseUrl}/api/v2/incidents/${incidentId}`, { headers: this.headers });
if (!response.ok) throw new Error(`Datadog API error: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
async getMonitors(limit = 30, state, tags) {
const params = new URLSearchParams({ page_size: limit.toString() });
if (state) params.append("group_states", state);
if (tags && tags.length > 0) params.append("tags", tags.join(","));
const response = await fetch(`${this.baseUrl}/api/v1/monitor?${params}`, { headers: this.headers });
if (!response.ok) throw new Error(`Datadog API error: ${response.status} ${response.statusText}`);
return response.json();
}
async getMonitor(monitorId) {
const response = await fetch(`${this.baseUrl}/api/v1/monitor/${monitorId}`, { headers: this.headers });
if (!response.ok) throw new Error(`Datadog API error: ${response.status} ${response.statusText}`);
return response.json();
}
async searchLogs(query, from, to, limit = 50) {
const body = {
filter: {
query,
from: from || (/* @__PURE__ */ new Date(Date.now() - 1440 * 60 * 1e3)).toISOString(),
to: to || (/* @__PURE__ */ new Date()).toISOString()
},
page: { limit },
sort: "-timestamp"
};
const response = await fetch(`${this.baseUrl}/api/v2/logs/events/search`, {
method: "POST",
headers: this.headers,
body: JSON.stringify(body)
});
if (!response.ok) throw new Error(`Datadog API error: ${response.status} ${response.statusText}`);
return (await response.json()).data || [];
}
async getMetrics(query, from, to) {
const params = new URLSearchParams({
query,
from: from.toString(),
to: to.toString()
});
const response = await fetch(`${this.baseUrl}/api/v1/query?${params}`, { headers: this.headers });
if (!response.ok) throw new Error(`Datadog API error: ${response.status} ${response.statusText}`);
return (await response.json()).series || [];
}
async getDowntimes(limit = 30) {
const response = await fetch(`${this.baseUrl}/api/v1/downtime`, { headers: this.headers });
if (!response.ok) throw new Error(`Datadog API error: ${response.status} ${response.statusText}`);
const result = await response.json();
return Array.isArray(result) ? result.slice(0, limit) : [];
}
async getHosts(limit = 30, filter) {
const params = new URLSearchParams({ count: limit.toString() });
if (filter) params.append("filter", filter);
const response = await fetch(`${this.baseUrl}/api/v1/hosts?${params}`, { headers: this.headers });
if (!response.ok) throw new Error(`Datadog API error: ${response.status} ${response.statusText}`);
return (await response.json()).host_list || [];
}
};
const DatadogConnectorConfig = mcpConnectorConfig({
name: "Datadog",
key: "datadog",
logo: "https://stackone-logos.com/api/datadog/filled/svg",
version: "1.0.0",
credentials: z.object({
apiKey: z.string().describe("Datadog API key from Organization Settings > API Keys :: 1234567890abcdef1234567890abcdef :: https://docs.datadoghq.com/account_management/api-app-keys/"),
appKey: z.string().describe("Datadog application key from Organization Settings > Application Keys :: 1234567890abcdef1234567890abcdef12345678")
}),
setup: z.object({ site: z.string().default("datadoghq.com").describe("Datadog site (e.g., datadoghq.com, datadoghq.eu) :: datadoghq.com") }),
examplePrompt: "Check my active incidents with SEV-1 severity, list all monitors that are alerting, and search for error logs from the last hour.",
tools: (tool) => ({
LIST_INCIDENTS: tool({
name: "datadog_list_incidents",
description: "List incidents with optional filtering",
schema: z.object({
limit: z.number().default(30).describe("Maximum number of incidents to return"),
state: z.string().optional().describe("Filter by incident state (active, stable, resolved)"),
severity: z.string().optional().describe("Filter by severity (SEV-1, SEV-2, SEV-3, SEV-4, SEV-5)")
}),
handler: async (args, context) => {
try {
const { apiKey, appKey } = await context.getCredentials();
const { site } = await context.getSetup();
const incidents = await new DatadogClient(apiKey, appKey, site).listIncidents(args.limit, args.state, args.severity);
return JSON.stringify(incidents, null, 2);
} catch (error) {
return `Failed to list incidents: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_INCIDENT: tool({
name: "datadog_get_incident",
description: "Get detailed information about a specific incident",
schema: z.object({ incidentId: z.string().describe("The incident ID to retrieve") }),
handler: async (args, context) => {
try {
const { apiKey, appKey } = await context.getCredentials();
const { site } = await context.getSetup();
const incident = await new DatadogClient(apiKey, appKey, site).getIncident(args.incidentId);
return JSON.stringify(incident, null, 2);
} catch (error) {
return `Failed to get incident: ${error instanceof Error ? error.message : String(error)}`;
}
}
}),
GET_MONITORS: tool({
name: "datadog_get_monitors",
description: "Get monitor statuses with optional filtering",
schema: z.object({
limit: z.number().default(30).describe("Maximum number of monitors to return"),
state: z.string().optional().describe("Filter by monitor state (Alert, Warn,