Skip to content
Draft
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
13 changes: 13 additions & 0 deletions .changeset/hungry-lines-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@scope3/agentic-client": minor
---

Add OutcomeAgent implementation with get_proposals and accept_proposal MCP tools

BREAKING CHANGE: Removed SimpleMediaAgent in favor of OutcomeAgent
- Deleted simple-media-agent files and replaced with outcome-agent implementation
- New binary: `outcome-agent` (previously `simple-media-agent`)
- New script: `npm run start:outcome-agent` to run the outcome agent server
- Implemented get-proposals handler with product filtering and budget optimization
- Implemented accept-proposal handler with validation and default acceptance
- Added comprehensive test coverage (32 tests total)
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"access": "public"
},
"bin": {
"simple-media-agent": "dist/simple-media-agent-server.js",
"outcome-agent": "dist/outcome-agent-server.js",
"scope3": "dist/cli.js"
},
"scripts": {
Expand All @@ -24,6 +24,7 @@
"lint": "eslint src --ext .ts",
"format": "prettier --write \"src/**/*.ts\"",
"type-check": "tsc --noEmit",
"start:outcome-agent": "npm run build && node dist/outcome-agent-server.js",
"generate-outcome-agent-types": "openapi-typescript outcome-agent-openapi.yaml -o src/types/outcome-agent-api.ts",
"generate-partner-api-types": "openapi-typescript partner-api.yaml -o src/types/partner-api.ts",
"generate-platform-api-types": "openapi-typescript platform-api.yaml -o src/types/platform-api.ts",
Expand Down
11 changes: 10 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@ export { Scope3AgenticClient } from './sdk';
// Legacy export for backwards compatibility
export { Scope3AgenticClient as Scope3SDK } from './sdk';
export { WebhookServer } from './webhook-server';
export { SimpleMediaAgent } from './simple-media-agent';
export { OutcomeAgent } from './outcome-agent';
export type { ClientConfig, ToolResponse, Environment } from './types';
export type { WebhookEvent, WebhookHandler, WebhookServerConfig } from './webhook-server';
export type {
OutcomeAgentConfig,
GetProposalsRequest,
GetProposalsResponse,
AcceptProposalRequest,
AcceptProposalResponse,
Proposal,
CampaignContext,
} from './outcome-agent/types';

export * from './resources/agents';
export * from './resources/assets';
Expand Down
29 changes: 29 additions & 0 deletions src/outcome-agent-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env node
import { OutcomeAgent } from './outcome-agent.js';

const scope3ApiKey = process.env.SCOPE3_API_KEY;
const scope3BaseUrl = process.env.SCOPE3_BASE_URL;

if (!scope3ApiKey) {
console.error('Error: SCOPE3_API_KEY environment variable is required');
process.exit(1);
}

const agent = new OutcomeAgent({
scope3ApiKey,
scope3BaseUrl,
name: 'outcome-agent',
version: '1.0.0',
});

agent.start().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

console.error(`
Outcome Agent
- Scope3 Base URL: ${scope3BaseUrl || 'https://api.agentic.scope3.com'}
- Protocol: MCP (stdio)
- Tools: get_proposals, accept_proposal
`);
112 changes: 112 additions & 0 deletions src/outcome-agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { FastMCP } from 'fastmcp';
import { z } from 'zod';
import { Scope3AgenticClient } from './sdk';
import type { OutcomeAgentConfig } from './outcome-agent/types';
import { getProposals } from './outcome-agent/get-proposals';
import { acceptProposal } from './outcome-agent/accept-proposal';

/**
* Outcome Agent that exposes MCP tools for proposal generation and management
* Called by Scope3 platform via MCP protocol
*/
export class OutcomeAgent {
private server: FastMCP;
private scope3: Scope3AgenticClient;
private config: Required<
Omit<OutcomeAgentConfig, 'name' | 'version'> & { name: string; version: string }
>;

constructor(config: OutcomeAgentConfig) {
this.config = {
scope3ApiKey: config.scope3ApiKey,
scope3BaseUrl: config.scope3BaseUrl || 'https://api.agentic.scope3.com',
name: config.name || 'outcome-agent',
version: (config.version as `${number}.${number}.${number}`) || '1.0.0',
};

this.server = new FastMCP({
name: this.config.name,
version: this.config.version as `${number}.${number}.${number}`,
});

this.scope3 = new Scope3AgenticClient({
apiKey: this.config.scope3ApiKey,
baseUrl: this.config.scope3BaseUrl,
});

this.setupTools();
}

private setupTools(): void {
// get_proposals tool
this.server.addTool({
name: 'get_proposals',
description:
'Get proposal recommendations from this outcome agent. Returns proposals with execution strategies, budget capacity, and pricing.',
parameters: z.object({
campaignId: z.string().describe('Campaign ID'),
seatId: z.string().describe('Seat/account ID'),
budgetRange: z
.object({
min: z.number().optional(),
max: z.number().optional(),
currency: z.string().optional(),
})
.optional()
.describe('Budget range with min/max and currency'),
startDate: z.string().optional().describe('Campaign start date (ISO 8601 UTC)'),
endDate: z.string().optional().describe('Campaign end date (ISO 8601 UTC)'),
channels: z
.array(z.enum(['display', 'video', 'native', 'audio', 'connected_tv']))
.optional()
.describe('Ad channels'),
countries: z.array(z.string()).optional().describe('ISO 3166-1 alpha-2 country codes'),
brief: z.string().optional().describe('Campaign description'),
products: z.array(z.object({}).passthrough()).optional().describe('Product references'),
propertyListIds: z.array(z.number()).optional().describe('Property list IDs'),
}),
execute: async (args) => {
const result = await getProposals(this.scope3, args);
return JSON.stringify(result, null, 2);
},
});

// accept_proposal tool
this.server.addTool({
name: 'accept_proposal',
description:
'Accept or decline a proposal assignment. Called when a proposal is accepted by users.',
parameters: z.object({
tacticId: z.string().describe('Tactic ID'),
proposalId: z.string().optional().describe('Proposal ID from get_proposals response'),
campaignContext: z
.object({
budget: z.number(),
budgetCurrency: z.string().optional(),
startDate: z.string(),
endDate: z.string(),
channel: z.enum(['display', 'video', 'native', 'audio', 'connected_tv']),
countries: z.array(z.string()).optional(),
creatives: z.array(z.object({}).passthrough()).optional(),
brandStandards: z.array(z.object({}).passthrough()).optional(),
})
.passthrough()
.describe('Campaign details including budget, schedule, targeting, and creatives'),
brandAgentId: z.string().describe('Brand agent ID'),
seatId: z.string().describe('Seat/account ID'),
customFields: z.record(z.unknown()).optional().describe('Custom advertiser fields'),
additional_info: z.record(z.unknown()).optional().describe('Additional metadata'),
}),
execute: async (args) => {
const result = await acceptProposal(this.scope3, args);
return JSON.stringify(result, null, 2);
},
});
}

async start(): Promise<void> {
await this.server.start({
transportType: 'stdio',
});
}
}
Loading