@hasna/microservices 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +63 -0
- package/bin/mcp.js +63 -0
- package/dist/index.js +63 -0
- package/microservices/microservice-ads/package.json +27 -0
- package/microservices/microservice-ads/src/cli/index.ts +605 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
- package/microservices/microservice-ads/src/db/database.ts +93 -0
- package/microservices/microservice-ads/src/db/migrations.ts +60 -0
- package/microservices/microservice-ads/src/index.ts +39 -0
- package/microservices/microservice-ads/src/mcp/index.ts +480 -0
- package/microservices/microservice-contracts/package.json +27 -0
- package/microservices/microservice-contracts/src/cli/index.ts +770 -0
- package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
- package/microservices/microservice-contracts/src/db/database.ts +93 -0
- package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
- package/microservices/microservice-contracts/src/index.ts +43 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
- package/microservices/microservice-domains/package.json +27 -0
- package/microservices/microservice-domains/src/cli/index.ts +691 -0
- package/microservices/microservice-domains/src/db/database.ts +93 -0
- package/microservices/microservice-domains/src/db/domains.ts +1164 -0
- package/microservices/microservice-domains/src/db/migrations.ts +60 -0
- package/microservices/microservice-domains/src/index.ts +65 -0
- package/microservices/microservice-domains/src/mcp/index.ts +536 -0
- package/microservices/microservice-hiring/package.json +27 -0
- package/microservices/microservice-hiring/src/cli/index.ts +741 -0
- package/microservices/microservice-hiring/src/db/database.ts +93 -0
- package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
- package/microservices/microservice-hiring/src/index.ts +80 -0
- package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
- package/microservices/microservice-payments/package.json +27 -0
- package/microservices/microservice-payments/src/cli/index.ts +609 -0
- package/microservices/microservice-payments/src/db/database.ts +93 -0
- package/microservices/microservice-payments/src/db/migrations.ts +81 -0
- package/microservices/microservice-payments/src/db/payments.ts +1204 -0
- package/microservices/microservice-payments/src/index.ts +51 -0
- package/microservices/microservice-payments/src/mcp/index.ts +683 -0
- package/microservices/microservice-payroll/package.json +27 -0
- package/microservices/microservice-payroll/src/cli/index.ts +643 -0
- package/microservices/microservice-payroll/src/db/database.ts +93 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
- package/microservices/microservice-payroll/src/index.ts +48 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
- package/microservices/microservice-shipping/package.json +27 -0
- package/microservices/microservice-shipping/src/cli/index.ts +606 -0
- package/microservices/microservice-shipping/src/db/database.ts +93 -0
- package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
- package/microservices/microservice-shipping/src/index.ts +53 -0
- package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
- package/microservices/microservice-social/package.json +27 -0
- package/microservices/microservice-social/src/cli/index.ts +689 -0
- package/microservices/microservice-social/src/db/database.ts +93 -0
- package/microservices/microservice-social/src/db/migrations.ts +88 -0
- package/microservices/microservice-social/src/db/social.ts +1046 -0
- package/microservices/microservice-social/src/index.ts +46 -0
- package/microservices/microservice-social/src/mcp/index.ts +655 -0
- package/microservices/microservice-subscriptions/package.json +27 -0
- package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
- package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
- package/microservices/microservice-subscriptions/src/index.ts +41 -0
- package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database connection for microservice-ads
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Database } from "bun:sqlite";
|
|
6
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
8
|
+
import { MIGRATIONS } from "./migrations.js";
|
|
9
|
+
|
|
10
|
+
let _db: Database | null = null;
|
|
11
|
+
|
|
12
|
+
function getDbPath(): string {
|
|
13
|
+
// Environment variable override
|
|
14
|
+
if (process.env["MICROSERVICES_DIR"]) {
|
|
15
|
+
return join(process.env["MICROSERVICES_DIR"], "microservice-ads", "data.db");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check for .microservices in current or parent directories
|
|
19
|
+
let dir = resolve(process.cwd());
|
|
20
|
+
while (true) {
|
|
21
|
+
const candidate = join(dir, ".microservices", "microservice-ads", "data.db");
|
|
22
|
+
const msDir = join(dir, ".microservices");
|
|
23
|
+
if (existsSync(msDir)) return candidate;
|
|
24
|
+
const parent = dirname(dir);
|
|
25
|
+
if (parent === dir) break;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Global fallback
|
|
30
|
+
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
31
|
+
return join(home, ".microservices", "microservice-ads", "data.db");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ensureDir(filePath: string): void {
|
|
35
|
+
const dir = dirname(resolve(filePath));
|
|
36
|
+
if (!existsSync(dir)) {
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getDatabase(): Database {
|
|
42
|
+
if (_db) return _db;
|
|
43
|
+
|
|
44
|
+
const dbPath = getDbPath();
|
|
45
|
+
ensureDir(dbPath);
|
|
46
|
+
|
|
47
|
+
_db = new Database(dbPath);
|
|
48
|
+
_db.exec("PRAGMA journal_mode = WAL");
|
|
49
|
+
_db.exec("PRAGMA foreign_keys = ON");
|
|
50
|
+
|
|
51
|
+
// Create migrations table
|
|
52
|
+
_db.exec(`
|
|
53
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
54
|
+
id INTEGER PRIMARY KEY,
|
|
55
|
+
name TEXT NOT NULL,
|
|
56
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
57
|
+
)
|
|
58
|
+
`);
|
|
59
|
+
|
|
60
|
+
// Apply pending migrations
|
|
61
|
+
const applied = _db
|
|
62
|
+
.query("SELECT id FROM _migrations ORDER BY id")
|
|
63
|
+
.all() as { id: number }[];
|
|
64
|
+
const appliedIds = new Set(applied.map((r) => r.id));
|
|
65
|
+
|
|
66
|
+
for (const migration of MIGRATIONS) {
|
|
67
|
+
if (appliedIds.has(migration.id)) continue;
|
|
68
|
+
|
|
69
|
+
_db.exec("BEGIN");
|
|
70
|
+
try {
|
|
71
|
+
_db.exec(migration.sql);
|
|
72
|
+
_db.prepare("INSERT INTO _migrations (id, name) VALUES (?, ?)").run(
|
|
73
|
+
migration.id,
|
|
74
|
+
migration.name
|
|
75
|
+
);
|
|
76
|
+
_db.exec("COMMIT");
|
|
77
|
+
} catch (error) {
|
|
78
|
+
_db.exec("ROLLBACK");
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Migration ${migration.id} (${migration.name}) failed: ${error instanceof Error ? error.message : String(error)}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return _db;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function closeDatabase(): void {
|
|
89
|
+
if (_db) {
|
|
90
|
+
_db.close();
|
|
91
|
+
_db = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface MigrationEntry {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
sql: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const MIGRATIONS: MigrationEntry[] = [
|
|
8
|
+
{
|
|
9
|
+
id: 1,
|
|
10
|
+
name: "initial_schema",
|
|
11
|
+
sql: `
|
|
12
|
+
CREATE TABLE IF NOT EXISTS campaigns (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
platform TEXT NOT NULL CHECK (platform IN ('google', 'meta', 'linkedin', 'tiktok')),
|
|
15
|
+
name TEXT NOT NULL,
|
|
16
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'active', 'paused', 'completed')),
|
|
17
|
+
budget_daily REAL NOT NULL DEFAULT 0,
|
|
18
|
+
budget_total REAL NOT NULL DEFAULT 0,
|
|
19
|
+
spend REAL NOT NULL DEFAULT 0,
|
|
20
|
+
impressions INTEGER NOT NULL DEFAULT 0,
|
|
21
|
+
clicks INTEGER NOT NULL DEFAULT 0,
|
|
22
|
+
conversions INTEGER NOT NULL DEFAULT 0,
|
|
23
|
+
roas REAL NOT NULL DEFAULT 0,
|
|
24
|
+
start_date TEXT,
|
|
25
|
+
end_date TEXT,
|
|
26
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
27
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
28
|
+
metadata TEXT NOT NULL DEFAULT '{}'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS ad_groups (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
campaign_id TEXT NOT NULL REFERENCES campaigns(id) ON DELETE CASCADE,
|
|
34
|
+
name TEXT NOT NULL,
|
|
35
|
+
targeting TEXT NOT NULL DEFAULT '{}',
|
|
36
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'active', 'paused', 'completed')),
|
|
37
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS ads (
|
|
41
|
+
id TEXT PRIMARY KEY,
|
|
42
|
+
ad_group_id TEXT NOT NULL REFERENCES ad_groups(id) ON DELETE CASCADE,
|
|
43
|
+
headline TEXT NOT NULL,
|
|
44
|
+
description TEXT,
|
|
45
|
+
creative_url TEXT,
|
|
46
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'active', 'paused', 'completed')),
|
|
47
|
+
metrics TEXT NOT NULL DEFAULT '{}',
|
|
48
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_campaigns_platform ON campaigns(platform);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_campaigns_status ON campaigns(status);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_campaigns_name ON campaigns(name);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_ad_groups_campaign ON ad_groups(campaign_id);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_ad_groups_status ON ad_groups(status);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_ads_ad_group ON ads(ad_group_id);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_ads_status ON ads(status);
|
|
58
|
+
`,
|
|
59
|
+
},
|
|
60
|
+
];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* microservice-ads — Ad campaign management microservice
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
createCampaign,
|
|
7
|
+
getCampaign,
|
|
8
|
+
listCampaigns,
|
|
9
|
+
updateCampaign,
|
|
10
|
+
deleteCampaign,
|
|
11
|
+
pauseCampaign,
|
|
12
|
+
resumeCampaign,
|
|
13
|
+
countCampaigns,
|
|
14
|
+
getCampaignStats,
|
|
15
|
+
getSpendByPlatform,
|
|
16
|
+
getPlatforms,
|
|
17
|
+
createAdGroup,
|
|
18
|
+
getAdGroup,
|
|
19
|
+
listAdGroups,
|
|
20
|
+
deleteAdGroup,
|
|
21
|
+
createAd,
|
|
22
|
+
getAd,
|
|
23
|
+
listAds,
|
|
24
|
+
deleteAd,
|
|
25
|
+
type Campaign,
|
|
26
|
+
type CreateCampaignInput,
|
|
27
|
+
type UpdateCampaignInput,
|
|
28
|
+
type ListCampaignsOptions,
|
|
29
|
+
type Platform,
|
|
30
|
+
type CampaignStatus,
|
|
31
|
+
type AdGroup,
|
|
32
|
+
type CreateAdGroupInput,
|
|
33
|
+
type Ad,
|
|
34
|
+
type CreateAdInput,
|
|
35
|
+
type CampaignStats,
|
|
36
|
+
type SpendByPlatform,
|
|
37
|
+
} from "./db/campaigns.js";
|
|
38
|
+
|
|
39
|
+
export { getDatabase, closeDatabase } from "./db/database.js";
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import {
|
|
7
|
+
createCampaign,
|
|
8
|
+
getCampaign,
|
|
9
|
+
listCampaigns,
|
|
10
|
+
updateCampaign,
|
|
11
|
+
deleteCampaign,
|
|
12
|
+
pauseCampaign,
|
|
13
|
+
resumeCampaign,
|
|
14
|
+
getCampaignStats,
|
|
15
|
+
getSpendByPlatform,
|
|
16
|
+
getPlatforms,
|
|
17
|
+
createAdGroup,
|
|
18
|
+
listAdGroups,
|
|
19
|
+
createAd,
|
|
20
|
+
listAds,
|
|
21
|
+
bulkPause,
|
|
22
|
+
bulkResume,
|
|
23
|
+
getRankedCampaigns,
|
|
24
|
+
checkBudgetStatus,
|
|
25
|
+
comparePlatforms,
|
|
26
|
+
exportCampaigns,
|
|
27
|
+
cloneCampaign,
|
|
28
|
+
getBudgetRemaining,
|
|
29
|
+
getAdGroupStats,
|
|
30
|
+
} from "../db/campaigns.js";
|
|
31
|
+
|
|
32
|
+
const server = new McpServer({
|
|
33
|
+
name: "microservice-ads",
|
|
34
|
+
version: "0.0.1",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const PlatformEnum = z.enum(["google", "meta", "linkedin", "tiktok"]);
|
|
38
|
+
const StatusEnum = z.enum(["draft", "active", "paused", "completed"]);
|
|
39
|
+
|
|
40
|
+
// --- Campaigns ---
|
|
41
|
+
|
|
42
|
+
server.registerTool(
|
|
43
|
+
"create_campaign",
|
|
44
|
+
{
|
|
45
|
+
title: "Create Campaign",
|
|
46
|
+
description: "Create a new ad campaign.",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
platform: PlatformEnum,
|
|
49
|
+
name: z.string(),
|
|
50
|
+
status: StatusEnum.optional(),
|
|
51
|
+
budget_daily: z.number().optional(),
|
|
52
|
+
budget_total: z.number().optional(),
|
|
53
|
+
start_date: z.string().optional(),
|
|
54
|
+
end_date: z.string().optional(),
|
|
55
|
+
metadata: z.record(z.unknown()).optional(),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async (params) => {
|
|
59
|
+
const campaign = createCampaign(params);
|
|
60
|
+
return { content: [{ type: "text", text: JSON.stringify(campaign, null, 2) }] };
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
server.registerTool(
|
|
65
|
+
"get_campaign",
|
|
66
|
+
{
|
|
67
|
+
title: "Get Campaign",
|
|
68
|
+
description: "Get a campaign by ID.",
|
|
69
|
+
inputSchema: { id: z.string() },
|
|
70
|
+
},
|
|
71
|
+
async ({ id }) => {
|
|
72
|
+
const campaign = getCampaign(id);
|
|
73
|
+
if (!campaign) {
|
|
74
|
+
return { content: [{ type: "text", text: `Campaign '${id}' not found.` }], isError: true };
|
|
75
|
+
}
|
|
76
|
+
return { content: [{ type: "text", text: JSON.stringify(campaign, null, 2) }] };
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
server.registerTool(
|
|
81
|
+
"list_campaigns",
|
|
82
|
+
{
|
|
83
|
+
title: "List Campaigns",
|
|
84
|
+
description: "List campaigns with optional filters.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
platform: PlatformEnum.optional(),
|
|
87
|
+
status: StatusEnum.optional(),
|
|
88
|
+
search: z.string().optional(),
|
|
89
|
+
limit: z.number().optional(),
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
async (params) => {
|
|
93
|
+
const campaigns = listCampaigns(params);
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: JSON.stringify({ campaigns, count: campaigns.length }, null, 2),
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
server.registerTool(
|
|
106
|
+
"update_campaign",
|
|
107
|
+
{
|
|
108
|
+
title: "Update Campaign",
|
|
109
|
+
description: "Update an existing campaign.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
id: z.string(),
|
|
112
|
+
platform: PlatformEnum.optional(),
|
|
113
|
+
name: z.string().optional(),
|
|
114
|
+
status: StatusEnum.optional(),
|
|
115
|
+
budget_daily: z.number().optional(),
|
|
116
|
+
budget_total: z.number().optional(),
|
|
117
|
+
spend: z.number().optional(),
|
|
118
|
+
impressions: z.number().optional(),
|
|
119
|
+
clicks: z.number().optional(),
|
|
120
|
+
conversions: z.number().optional(),
|
|
121
|
+
roas: z.number().optional(),
|
|
122
|
+
start_date: z.string().optional(),
|
|
123
|
+
end_date: z.string().optional(),
|
|
124
|
+
metadata: z.record(z.unknown()).optional(),
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
async ({ id, ...input }) => {
|
|
128
|
+
const campaign = updateCampaign(id, input);
|
|
129
|
+
if (!campaign) {
|
|
130
|
+
return { content: [{ type: "text", text: `Campaign '${id}' not found.` }], isError: true };
|
|
131
|
+
}
|
|
132
|
+
return { content: [{ type: "text", text: JSON.stringify(campaign, null, 2) }] };
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
server.registerTool(
|
|
137
|
+
"delete_campaign",
|
|
138
|
+
{
|
|
139
|
+
title: "Delete Campaign",
|
|
140
|
+
description: "Delete a campaign by ID.",
|
|
141
|
+
inputSchema: { id: z.string() },
|
|
142
|
+
},
|
|
143
|
+
async ({ id }) => {
|
|
144
|
+
const deleted = deleteCampaign(id);
|
|
145
|
+
return { content: [{ type: "text", text: JSON.stringify({ id, deleted }) }] };
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
server.registerTool(
|
|
150
|
+
"pause_campaign",
|
|
151
|
+
{
|
|
152
|
+
title: "Pause Campaign",
|
|
153
|
+
description: "Pause an active campaign.",
|
|
154
|
+
inputSchema: { id: z.string() },
|
|
155
|
+
},
|
|
156
|
+
async ({ id }) => {
|
|
157
|
+
const campaign = pauseCampaign(id);
|
|
158
|
+
if (!campaign) {
|
|
159
|
+
return { content: [{ type: "text", text: `Campaign '${id}' not found.` }], isError: true };
|
|
160
|
+
}
|
|
161
|
+
return { content: [{ type: "text", text: JSON.stringify(campaign, null, 2) }] };
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
server.registerTool(
|
|
166
|
+
"resume_campaign",
|
|
167
|
+
{
|
|
168
|
+
title: "Resume Campaign",
|
|
169
|
+
description: "Resume a paused campaign.",
|
|
170
|
+
inputSchema: { id: z.string() },
|
|
171
|
+
},
|
|
172
|
+
async ({ id }) => {
|
|
173
|
+
const campaign = resumeCampaign(id);
|
|
174
|
+
if (!campaign) {
|
|
175
|
+
return { content: [{ type: "text", text: `Campaign '${id}' not found.` }], isError: true };
|
|
176
|
+
}
|
|
177
|
+
return { content: [{ type: "text", text: JSON.stringify(campaign, null, 2) }] };
|
|
178
|
+
}
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
server.registerTool(
|
|
182
|
+
"campaign_stats",
|
|
183
|
+
{
|
|
184
|
+
title: "Campaign Stats",
|
|
185
|
+
description: "Get aggregate campaign statistics.",
|
|
186
|
+
inputSchema: {},
|
|
187
|
+
},
|
|
188
|
+
async () => {
|
|
189
|
+
const stats = getCampaignStats();
|
|
190
|
+
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
server.registerTool(
|
|
195
|
+
"spend_by_platform",
|
|
196
|
+
{
|
|
197
|
+
title: "Spend by Platform",
|
|
198
|
+
description: "Get total spend broken down by platform.",
|
|
199
|
+
inputSchema: {},
|
|
200
|
+
},
|
|
201
|
+
async () => {
|
|
202
|
+
const spend = getSpendByPlatform();
|
|
203
|
+
return { content: [{ type: "text", text: JSON.stringify(spend, null, 2) }] };
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
server.registerTool(
|
|
208
|
+
"list_platforms",
|
|
209
|
+
{
|
|
210
|
+
title: "List Platforms",
|
|
211
|
+
description: "List platforms that have campaigns.",
|
|
212
|
+
inputSchema: {},
|
|
213
|
+
},
|
|
214
|
+
async () => {
|
|
215
|
+
const platforms = getPlatforms();
|
|
216
|
+
return { content: [{ type: "text", text: JSON.stringify(platforms, null, 2) }] };
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
server.registerTool(
|
|
221
|
+
"list_providers",
|
|
222
|
+
{
|
|
223
|
+
title: "List Providers",
|
|
224
|
+
description: "List all supported ad providers.",
|
|
225
|
+
inputSchema: {},
|
|
226
|
+
},
|
|
227
|
+
async () => {
|
|
228
|
+
const providers = ["google", "meta", "linkedin", "tiktok"];
|
|
229
|
+
return { content: [{ type: "text", text: JSON.stringify(providers, null, 2) }] };
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// --- Ad Groups ---
|
|
234
|
+
|
|
235
|
+
server.registerTool(
|
|
236
|
+
"create_ad_group",
|
|
237
|
+
{
|
|
238
|
+
title: "Create Ad Group",
|
|
239
|
+
description: "Create a new ad group within a campaign.",
|
|
240
|
+
inputSchema: {
|
|
241
|
+
campaign_id: z.string(),
|
|
242
|
+
name: z.string(),
|
|
243
|
+
targeting: z.record(z.unknown()).optional(),
|
|
244
|
+
status: StatusEnum.optional(),
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
async (params) => {
|
|
248
|
+
const adGroup = createAdGroup(params);
|
|
249
|
+
return { content: [{ type: "text", text: JSON.stringify(adGroup, null, 2) }] };
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
server.registerTool(
|
|
254
|
+
"list_ad_groups",
|
|
255
|
+
{
|
|
256
|
+
title: "List Ad Groups",
|
|
257
|
+
description: "List ad groups, optionally filtered by campaign.",
|
|
258
|
+
inputSchema: {
|
|
259
|
+
campaign_id: z.string().optional(),
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
async ({ campaign_id }) => {
|
|
263
|
+
const adGroups = listAdGroups(campaign_id);
|
|
264
|
+
return {
|
|
265
|
+
content: [
|
|
266
|
+
{
|
|
267
|
+
type: "text",
|
|
268
|
+
text: JSON.stringify({ ad_groups: adGroups, count: adGroups.length }, null, 2),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// --- Ads ---
|
|
276
|
+
|
|
277
|
+
server.registerTool(
|
|
278
|
+
"create_ad",
|
|
279
|
+
{
|
|
280
|
+
title: "Create Ad",
|
|
281
|
+
description: "Create a new ad within an ad group.",
|
|
282
|
+
inputSchema: {
|
|
283
|
+
ad_group_id: z.string(),
|
|
284
|
+
headline: z.string(),
|
|
285
|
+
description: z.string().optional(),
|
|
286
|
+
creative_url: z.string().optional(),
|
|
287
|
+
status: StatusEnum.optional(),
|
|
288
|
+
metrics: z.record(z.unknown()).optional(),
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
async (params) => {
|
|
292
|
+
const ad = createAd(params);
|
|
293
|
+
return { content: [{ type: "text", text: JSON.stringify(ad, null, 2) }] };
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
server.registerTool(
|
|
298
|
+
"list_ads",
|
|
299
|
+
{
|
|
300
|
+
title: "List Ads",
|
|
301
|
+
description: "List ads, optionally filtered by ad group.",
|
|
302
|
+
inputSchema: {
|
|
303
|
+
ad_group_id: z.string().optional(),
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
async ({ ad_group_id }) => {
|
|
307
|
+
const ads = listAds(ad_group_id);
|
|
308
|
+
return {
|
|
309
|
+
content: [
|
|
310
|
+
{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: JSON.stringify({ ads, count: ads.length }, null, 2),
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// --- QoL Tools ---
|
|
320
|
+
|
|
321
|
+
// 1. Bulk pause/resume
|
|
322
|
+
server.registerTool(
|
|
323
|
+
"bulk_pause_campaigns",
|
|
324
|
+
{
|
|
325
|
+
title: "Bulk Pause Campaigns",
|
|
326
|
+
description: "Pause all active campaigns on a specific platform.",
|
|
327
|
+
inputSchema: {
|
|
328
|
+
platform: PlatformEnum,
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
async ({ platform }) => {
|
|
332
|
+
const result = bulkPause(platform);
|
|
333
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
server.registerTool(
|
|
338
|
+
"bulk_resume_campaigns",
|
|
339
|
+
{
|
|
340
|
+
title: "Bulk Resume Campaigns",
|
|
341
|
+
description: "Resume all paused campaigns on a specific platform.",
|
|
342
|
+
inputSchema: {
|
|
343
|
+
platform: PlatformEnum,
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
async ({ platform }) => {
|
|
347
|
+
const result = bulkResume(platform);
|
|
348
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
349
|
+
}
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
// 2. Performance ranking
|
|
353
|
+
server.registerTool(
|
|
354
|
+
"ranked_campaigns",
|
|
355
|
+
{
|
|
356
|
+
title: "Ranked Campaigns",
|
|
357
|
+
description: "Get campaigns ranked by a performance metric (roas, ctr, or spend).",
|
|
358
|
+
inputSchema: {
|
|
359
|
+
sort_by: z.enum(["roas", "ctr", "spend"]).optional(),
|
|
360
|
+
limit: z.number().optional(),
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
async ({ sort_by, limit }) => {
|
|
364
|
+
const campaigns = getRankedCampaigns(sort_by || "roas", limit || 10);
|
|
365
|
+
return { content: [{ type: "text", text: JSON.stringify(campaigns, null, 2) }] };
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// 3. Budget alerts
|
|
370
|
+
server.registerTool(
|
|
371
|
+
"check_budget",
|
|
372
|
+
{
|
|
373
|
+
title: "Check Budget Status",
|
|
374
|
+
description: "Check if a campaign is over budget and get remaining budget details.",
|
|
375
|
+
inputSchema: { id: z.string() },
|
|
376
|
+
},
|
|
377
|
+
async ({ id }) => {
|
|
378
|
+
const status = checkBudgetStatus(id);
|
|
379
|
+
if (!status) {
|
|
380
|
+
return { content: [{ type: "text", text: `Campaign '${id}' not found.` }], isError: true };
|
|
381
|
+
}
|
|
382
|
+
return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
// 4. Cross-platform comparison
|
|
387
|
+
server.registerTool(
|
|
388
|
+
"compare_platforms",
|
|
389
|
+
{
|
|
390
|
+
title: "Compare Platforms",
|
|
391
|
+
description: "Compare ROAS, CPA, and spend across all platforms side-by-side.",
|
|
392
|
+
inputSchema: {},
|
|
393
|
+
},
|
|
394
|
+
async () => {
|
|
395
|
+
const comparison = comparePlatforms();
|
|
396
|
+
return { content: [{ type: "text", text: JSON.stringify(comparison, null, 2) }] };
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// 5. CSV export
|
|
401
|
+
server.registerTool(
|
|
402
|
+
"export_campaigns",
|
|
403
|
+
{
|
|
404
|
+
title: "Export Campaigns",
|
|
405
|
+
description: "Export all campaigns in CSV or JSON format.",
|
|
406
|
+
inputSchema: {
|
|
407
|
+
format: z.enum(["csv", "json"]).optional(),
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
async ({ format }) => {
|
|
411
|
+
const output = exportCampaigns(format || "csv");
|
|
412
|
+
return { content: [{ type: "text", text: output }] };
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
// 6. Campaign cloning
|
|
417
|
+
server.registerTool(
|
|
418
|
+
"clone_campaign",
|
|
419
|
+
{
|
|
420
|
+
title: "Clone Campaign",
|
|
421
|
+
description: "Clone a campaign with all its ad groups and ads.",
|
|
422
|
+
inputSchema: {
|
|
423
|
+
id: z.string(),
|
|
424
|
+
new_name: z.string(),
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
async ({ id, new_name }) => {
|
|
428
|
+
const cloned = cloneCampaign(id, new_name);
|
|
429
|
+
if (!cloned) {
|
|
430
|
+
return { content: [{ type: "text", text: `Campaign '${id}' not found.` }], isError: true };
|
|
431
|
+
}
|
|
432
|
+
return { content: [{ type: "text", text: JSON.stringify(cloned, null, 2) }] };
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
// 7. Budget remaining
|
|
437
|
+
server.registerTool(
|
|
438
|
+
"budget_remaining",
|
|
439
|
+
{
|
|
440
|
+
title: "Budget Remaining",
|
|
441
|
+
description: "Show daily and total budget remaining for a campaign.",
|
|
442
|
+
inputSchema: { id: z.string() },
|
|
443
|
+
},
|
|
444
|
+
async ({ id }) => {
|
|
445
|
+
const remaining = getBudgetRemaining(id);
|
|
446
|
+
if (!remaining) {
|
|
447
|
+
return { content: [{ type: "text", text: `Campaign '${id}' not found.` }], isError: true };
|
|
448
|
+
}
|
|
449
|
+
return { content: [{ type: "text", text: JSON.stringify(remaining, null, 2) }] };
|
|
450
|
+
}
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// 8. Ad group stats
|
|
454
|
+
server.registerTool(
|
|
455
|
+
"ad_group_stats",
|
|
456
|
+
{
|
|
457
|
+
title: "Ad Group Stats",
|
|
458
|
+
description: "Get aggregated metrics for all ads within an ad group.",
|
|
459
|
+
inputSchema: { ad_group_id: z.string() },
|
|
460
|
+
},
|
|
461
|
+
async ({ ad_group_id }) => {
|
|
462
|
+
const stats = getAdGroupStats(ad_group_id);
|
|
463
|
+
if (!stats) {
|
|
464
|
+
return { content: [{ type: "text", text: `Ad group '${ad_group_id}' not found.` }], isError: true };
|
|
465
|
+
}
|
|
466
|
+
return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
|
|
467
|
+
}
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
// --- Start ---
|
|
471
|
+
async function main() {
|
|
472
|
+
const transport = new StdioServerTransport();
|
|
473
|
+
await server.connect(transport);
|
|
474
|
+
console.error("microservice-ads MCP server running on stdio");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
main().catch((error) => {
|
|
478
|
+
console.error("Fatal error:", error);
|
|
479
|
+
process.exit(1);
|
|
480
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna/microservice-contracts",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Contract and agreement management microservice with SQLite — manage contracts, clauses, and reminders",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"microservice-contracts": "./src/cli/index.ts",
|
|
8
|
+
"microservice-contracts-mcp": "./src/mcp/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "bun run ./src/cli/index.ts",
|
|
15
|
+
"test": "bun test"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
19
|
+
"commander": "^12.1.0",
|
|
20
|
+
"zod": "^3.24.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "Apache-2.0",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org",
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|