Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/webapp/app/components/icon-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Todoist } from "./icons/todoist";
import { Ghost } from "./icons/ghost";
import { Fireflies } from "./icons/fireflies";
import { Whatsapp } from "./icons/whatsapp";
import { MetaAds } from "./icons/meta-ads";
import { Metabase } from "./icons/metabase";
import { Resend } from "./icons/resend";
import { Ynab } from "./icons/ynab";
Expand Down Expand Up @@ -77,6 +78,7 @@ export const ICON_MAPPING = {
ghost: Ghost,
fireflies: Fireflies,
whatsapp: Whatsapp,
"meta-ads": MetaAds,
metabase: Metabase,
resend: Resend,
ynab: Ynab,
Expand Down
39 changes: 39 additions & 0 deletions apps/webapp/app/components/icons/meta-ads.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { IconProps } from './types';

export function MetaAds({ size = 18, className }: IconProps) {
return (
<svg
width={size}
height={size}
className={className}
viewBox="0 0 36 36"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 0C8.059 0 0 8.059 0 18s8.059 18 18 18 18-8.059 18-18S27.941 0 18 0z"
fill="#1877F2"
/>
<path
d="M7.5 18c0-5.247 4.253-9.5 10.5-9.5 2.56 0 4.8.897 6.512 2.37L22.17 12.71C20.937 11.641 19.52 11 18 11c-3.866 0-7 3.134-7 7 0 2.34 1.145 4.41 2.906 5.68L12.05 25.5C9.395 23.72 7.5 21.06 7.5 18z"
fill="white"
/>
<path
d="M14 18c0-2.21 1.79-4 4-4 1.05 0 2 .405 2.71 1.065l1.845-1.845A6.96 6.96 0 0018 11c-3.866 0-7 3.134-7 7 0 2.34 1.145 4.41 2.906 5.68l1.856-1.82A3.985 3.985 0 0114 18z"
fill="white"
/>
<path
d="M18 14c1.05 0 2 .405 2.71 1.065l1.845-1.845A6.96 6.96 0 0018 11v3z"
fill="#1877F2"
/>
<path
d="M28.5 18c0 5.247-4.253 9.5-10.5 9.5-2.56 0-4.8-.897-6.512-2.37l1.842-1.82C14.563 24.359 16.18 25 18 25c3.866 0 7-3.134 7-7 0-2.34-1.145-4.41-2.906-5.68l1.856-1.82C26.605 12.28 28.5 14.94 28.5 18z"
fill="white"
/>
<path
d="M22 18c0 2.21-1.79 4-4 4-.95 0-1.82-.333-2.5-.88l-1.856 1.82A6.948 6.948 0 0018 25c3.866 0 7-3.134 7-7 0-2.34-1.145-4.41-2.906-5.68L20.25 14.14A3.985 3.985 0 0122 18z"
fill="white"
/>
</svg>
);
}
3 changes: 3 additions & 0 deletions integrations/meta-ads/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
bin
57 changes: 57 additions & 0 deletions integrations/meta-ads/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@core/meta-ads",
"version": "0.1.0",
"description": "Meta Ads extension for CORE",
"main": "./bin/index.js",
"module": "./bin/index.mjs",
"type": "module",
"files": [
"meta-ads",
"bin"
],
"bin": {
"meta-ads": "./bin/index.js"
},
"scripts": {
"build": "rimraf dist && bun build src/index.ts --outfile dist/index.js --target node --minify",
"lint": "eslint --ext js,ts,tsx src/ --fix",
"prettier": "prettier --config .prettierrc --write .",
"copy:spec": "cp spec.json bin/"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/preset-typescript": "^7.26.0",
"@prisma/client": "^7.4.0",
"@types/node": "^18.0.20",
"@types/pg": "^8.10.9",
"eslint": "^9.24.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-unused-imports": "^2.0.0",
"ncc": "0.3.6",
"prettier": "^3.4.2",
"rimraf": "^3.0.2",
"tslib": "^2.8.1",
"tsup": "^8.0.1",
"tsx": "4.20.6",
"typescript": "^4.7.2"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@core/database": "file:../../packages/database",
"@modelcontextprotocol/sdk": "^0.4.0",
"@redplanethq/sdk": "0.1.9",
"axios": "^1.7.9",
"commander": "^12.0.0",
"pg": "^8.11.3",
"react-query": "^3.39.3",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.22.1"
}
}
71 changes: 71 additions & 0 deletions integrations/meta-ads/scripts/register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import pg from 'pg';
const { Client } = pg;

async function main() {
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
console.error('DATABASE_URL environment variable is required');
process.exit(1);
}

const client = new Client({
connectionString,
});

const spec = {
name: 'Meta Ads',
key: 'meta-ads',
description:
'Connect your Meta Ads account to manage campaigns, ad sets, ads, and retrieve performance insights across Facebook and Instagram.',
icon: 'meta-ads',
mcp: {
type: 'cli',
},
auth: {
OAuth2: {
authorization_url: 'https://www.facebook.com/v19.0/dialog/oauth',
token_url: 'https://graph.facebook.com/v19.0/oauth/access_token',
scopes: ['ads_read', 'ads_management', 'read_insights'],
scope_separator: ',',
},
},
};

try {
await client.connect();

await client.query(
`
INSERT INTO core."IntegrationDefinitionV2" ("id", "name", "slug", "description", "icon", "spec", "config", "version", "url", "updatedAt", "createdAt")
VALUES (gen_random_uuid(), 'Meta Ads', 'meta-ads', $4, 'meta-ads', $1, $2, '0.1.0', $3, NOW(), NOW())
ON CONFLICT (name) DO UPDATE SET
"slug" = EXCLUDED."slug",
"description" = EXCLUDED."description",
"icon" = EXCLUDED."icon",
"spec" = EXCLUDED."spec",
"config" = EXCLUDED."config",
"version" = EXCLUDED."version",
"url" = EXCLUDED."url",
"updatedAt" = NOW()
RETURNING *;
`,
[
JSON.stringify(spec),
JSON.stringify({
clientId: process.env.META_ADS_CLIENT_ID,
clientSecret: process.env.META_ADS_CLIENT_SECRET,
}),
'../../integrations/meta-ads/bin/index.cjs',
'Connect your Meta Ads account to manage campaigns, ad sets, ads, and retrieve performance insights across Facebook and Instagram.',
],
);

console.log('Meta Ads integration registered successfully in the database.');
} catch (error) {
console.error('Error registering Meta Ads integration:', error);
} finally {
await client.end();
}
}

main().catch(console.error);
46 changes: 46 additions & 0 deletions integrations/meta-ads/src/account-create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import axios from 'axios';

const META_API_BASE = 'https://graph.facebook.com/v19.0';

export async function integrationCreate(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data: any,
) {
const { oauthResponse } = data;
const accessToken = oauthResponse.access_token;

const userResponse = await axios.get(`${META_API_BASE}/me`, {
params: {
access_token: accessToken,
fields: 'id,name,email',
},
});

const user = userResponse.data;

const integrationConfiguration = {
access_token: accessToken,
token_type: oauthResponse.token_type || 'Bearer',
scope: oauthResponse.scope,
userId: user.id,
userName: user.name,
userEmail: user.email,
};

return [
{
type: 'account',
data: {
settings: {
userName: user.name,
userEmail: user.email,
},
accountId: user.id,
config: {
...integrationConfiguration,
mcp: { tokens: { access_token: accessToken } },
},
},
},
];
}
84 changes: 84 additions & 0 deletions integrations/meta-ads/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { integrationCreate } from './account-create';

import {
IntegrationCLI,
IntegrationEventPayload,
IntegrationEventType,
Spec,
} from '@redplanethq/sdk';
import { callTool, getTools } from './mcp';
import { fileURLToPath } from 'url';

export async function run(eventPayload: IntegrationEventPayload) {
switch (eventPayload.event) {
case IntegrationEventType.SETUP:
return await integrationCreate(eventPayload.eventBody);

case IntegrationEventType.GET_TOOLS: {
try {
const tools = await getTools();
return tools;
} catch (e: any) {
return { message: `Error ${e.message}` };
}
}

case IntegrationEventType.CALL_TOOL: {
const integrationDefinition = eventPayload.integrationDefinition;

if (!integrationDefinition) {
return null;
}

const config = eventPayload.config as any;
const { name, arguments: args } = eventPayload.eventBody;

const result = await callTool(name, args, config);

return result;
}

default:
return { message: `The event payload type is ${eventPayload.event}` };
}
}

class MetaAdsCLI extends IntegrationCLI {
constructor() {
super('meta-ads', '0.1.0');
}

protected async handleEvent(eventPayload: IntegrationEventPayload): Promise<any> {
return await run(eventPayload);
}

protected async getSpec(): Promise<Spec> {
return {
name: 'Meta Ads',
key: 'meta-ads',
description:
'Connect your Meta Ads account to manage campaigns, ad sets, ads, and retrieve performance insights across Facebook and Instagram.',
icon: 'meta-ads',
mcp: {
type: 'cli',
},
auth: {
OAuth2: {
authorization_url: 'https://www.facebook.com/v19.0/dialog/oauth',
token_url: 'https://graph.facebook.com/v19.0/oauth/access_token',
scopes: ['ads_read', 'ads_management', 'read_insights'],
scope_separator: ',',
},
},
};
}
}

function main() {
const metaAdsCLI = new MetaAdsCLI();
metaAdsCLI.parse();
}

if (process.argv[1] === fileURLToPath(import.meta.url)) {
main();
}
1 change: 1 addition & 0 deletions integrations/meta-ads/src/mcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { getTools, callTool } from './mcp/index.js';
Loading