Build a PR & Media Monitoring MCP Server for Agentic Communications Teams
Inspired by Featured's July 2026 MCP server for PR agencies, this dispatch builds pr-media-mcp, a FastMCP TypeScript server that lets agents monitor press mentions, track journalist and outlet sentiment, manage pitch outreach queues, and draft response drafts. Read tools are always available; write tools — send pitch, update contact — are gated behind OAuth 2.0 scopes plus a human approval gate, and every action lands in an append-only audit trail.
Deepak Bagada
CEO, SaaSNext
- Featured's July 2026 PR MCP server pushed press monitoring into agentic workflows, and pr-media-mcp turns that into a reusable FastMCP server.
- Read tools — mention search, sentiment, pitch queue status, response drafts — are always available to authenticated agents.
- Write tools — send_pitch and update_contact — return approval intents, never completion states, and require OAuth scopes plus a human approval gate.
- Every mention scan, blocked attempt, approval decision, and executed pitch is written to an append-only audit trail.
- OAuth scopes are enforced from the token at runtime with audience validation, and agent credentials are isolated from upstream PR SaaS service accounts.
By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.
In July 2026, the PR industry crossed a threshold that communications teams had been circling for a year: the media monitoring stack itself became agentic. Featured, the PR software company, shipped an MCP server aimed squarely at PR agencies, and the pattern spread through the industry within weeks. Monitoring platforms began exposing press mentions, journalist databases, outlet profiles, and outreach queues as tools an AI agent could call directly, instead of dashboard widgets a human had to click. The appeal for an account team is immediate — an agent that scans morning mentions, gauges a journalist's sentiment toward a client, and prepares a pitch draft saves hours every single day.
But PR is also the arena where agent actions are externally visible and reputation-bearing. Monitoring is safe. Sending a pitch is not. The difference between the two is the boundary you build around the write tools, and that boundary is the subject of this dispatch. You will implement pr-media-mcp, a FastMCP TypeScript server for agentic communications teams that monitors press mentions, tracks journalist and outlet sentiment, manages pitch outreach queues, and drafts response drafts. Read tools are always available to any authenticated agent. Write tools — sending a pitch, updating a contact record — are gated behind OAuth 2.0 scopes plus a human approval gate. Every action, read or write, lands in an append-only audit trail. Keep the MCP directory open while you build — a server like this is exactly the entry it should carry.
Why communications teams need agentic monitoring
The core PR workload is read-heavy. A team runs mention searches across wire services, trade publications, and social feeds; it watches sentiment drift for a client over weeks; it maintains a living database of journalists, their beats, and their recent coverage; and it tracks a pitch pipeline from research to draft to send. All of that is monitoring, and monitoring is where agents earn their keep with zero downside. An agent can watch a thousand outlets while an account executive sleeps, flag a spike in negative mentions, and surface the first draft of a response before the client even asks. The latest-ai-news coverage of the July PR-MCP wave kept coming back to the same arithmetic: agencies spend 60 to 70 percent of their day on monitoring and coordination, and almost none of it requires a human judgment call.
The coordination half is where the boundary lives. Researching a journalist, drafting a pitch, queuing it for review — those are fine for an agent to prepare. Hitting send is not. A pitch sent from the wrong mailbox, to the wrong outlet, or with a client's unreleased number in the body is not a bug you can roll back; it is a burned relationship and a very awkward Monday call. That is why the July 2026 PR-MCP wave settled on a common shape: read tools open, write tools gated, everything audited. The workflows library keeps a companion template for approval-driven outreach if you want the pipeline version of the same discipline.
Architecture
The server forks every request at one point: is this a read or a write? Reads take the fast lane to the monitoring APIs. Writes pass through a scope check and an approval gate that exist outside the model. Everything reports to the same append-only audit trail, which makes the server's behavior explainable after the fact and contestable in a review.
flowchart TD
A[MCP client agent] -->|Bearer token + scopes| B[pr-media-mcp FastMCP server]
B --> C{Read or write tool?}
C -->|read| D[Read dispatcher]
D --> E[Press mention search]
D --> F[Sentiment tracker]
D --> G[Pitch queue status]
D --> H[Response draft generator]
B -->|write| I{Scope token present?}
I -->|no| J[403 missing write scope]
I -->|yes| K{Human approval gate}
K -->|rejected| L[Audit: write blocked]
K -->|approved| M[Write executor]
M --> N[send_pitch -> outreach API]
M --> O[update_contact -> CRM]
N --> P[Append-only audit trail]
O --> P
E --> P
F --> P
G --> P
The design makes one thing structural: the model never crosses the read-write fork on its own. It can prepare, queue, and draft forever. The moment an action would touch an external mailbox or a shared CRM record, the fork hands control to a human.
The TypeScript implementation
The server uses @modelcontextprotocol/sdk with McpServer and zod, and each tool is registered through registerTool. Read tools and write tools live in separate registration methods, so the boundary is enforced in the code, not in a README.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
type AgentContext = {
principalId: string;
scopes: string[];
};
export class PrMediaMcp {
private server: McpServer;
private mentions: MentionsApi;
private sentiment: SentimentApi;
private outreach: OutreachApi;
private crm: CrmApi;
private audit: AuditTrail;
private approvals: ApprovalService;
constructor() {
this.mentions = new MentionsApi();
this.sentiment = new SentimentApi();
this.outreach = new OutreachApi();
this.crm = new CrmApi();
this.audit = new AuditTrail();
this.approvals = new ApprovalService();
this.server = new McpServer({ name: "pr-media-mcp", version: "1.0.0" });
this.registerReadTools();
this.registerWriteTools();
}
private registerReadTools() {
this.server.registerTool(
"mention_search",
"Search press mentions across outlets and feeds. Always available to authenticated agents. Returns article, outlet, and snippet.",
{
query: z.string().describe("Mention search query"),
since: z.string().optional().describe("ISO date to search from"),
limit: z.number().int().min(1).max(50).default(20)
},
async ({ query, since, limit }, extra) => {
const ctx = this.contextFrom(extra);
this.audit.log(ctx.principalId, "mention_search", { query }, "read");
return this.mentions.search(query, since, limit);
}
);
this.server.registerTool(
"mention_sentiment",
"Return sentiment trend for a client or topic. Read-only classification of recent coverage.",
{
topic: z.string().describe("Client, brand, or topic"),
days: z.number().int().min(1).max(90).default(30)
},
async ({ topic, days }, extra) => {
const ctx = this.contextFrom(extra);
this.audit.log(ctx.principalId, "mention_sentiment", { topic }, "read");
return this.sentiment.trend(topic, days);
}
);
this.server.registerTool(
"pitch_queue_status",
"List current pitch queue entries with status. Read-only view of outreach state.",
{ principalId: z.string().optional().describe("Restrict to a queue owner") },
async ({ principalId }, extra) => {
const ctx = this.contextFrom(extra);
this.audit.log(ctx.principalId, "pitch_queue_status", {}, "read");
return this.outreach.queue(principalId ?? ctx.principalId);
}
);
this.server.registerTool(
"draft_response",
"Generate a draft response to a mention for a human to review. Produces text only; never sends.",
{
mentionId: z.string().describe("Mention to respond to"),
tone: z.enum(["neutral", "apologetic", "authoritative"]).default("neutral")
},
async ({ mentionId, tone }, extra) => {
const ctx = this.contextFrom(extra);
this.audit.log(ctx.principalId, "draft_response", { mentionId }, "read");
return this.mentions.draft(mentionId, tone);
}
);
}
private registerWriteTools() {
this.server.registerTool(
"send_pitch",
"PROPOSE sending a pitch to a journalist. Sending requires the pitch:send scope AND a human approval before anything leaves the mailbox.",
{
contactId: z.string().describe("Journalist contact ID"),
pitchId: z.string().describe("Pitch draft ID"),
scheduledAt: z.string().optional().describe("Optional send time in ISO 8601")
},
async ({ contactId, pitchId, scheduledAt }, extra) => {
const ctx = this.contextFrom(extra);
if (!ctx.scopes.includes("pitch:send")) {
throw new Error("Missing pitch:send scope");
}
this.audit.log(ctx.principalId, "send_pitch", { contactId }, "write-proposed", "high");
return this.approvals.createIntent("send_pitch", ctx.principalId, { contactId, pitchId, scheduledAt });
}
);
this.server.registerTool(
"update_contact",
"PROPOSE updating a journalist or outlet contact record in the CRM. Requires contacts:write scope plus approval.",
{
contactId: z.string().describe("CRM contact ID"),
fields: z.record(z.string()).describe("Fields to update, e.g. beat, outlet, notes")
},
async ({ contactId, fields }, extra) => {
const ctx = this.contextFrom(extra);
if (!ctx.scopes.includes("contacts:write")) {
throw new Error("Missing contacts:write scope");
}
this.audit.log(ctx.principalId, "update_contact", { contactId }, "write-proposed");
return this.approvals.createIntent("update_contact", ctx.principalId, { contactId, fields });
}
);
}
private contextFrom(extra: any): AgentContext {
return decodeAndValidateToken(extra?.session?.auth?.token);
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}
Three choices carry the design. Write tools return approval intents, never completion states, so the model cannot mistake "prepared" for "sent." Every write re-checks the scope on the resolved token at runtime rather than trusting a client-declared scope. And the audit trail records the proposal before execution, so blocked attempts are as visible as successful sends.
inputSchema
The schemas below are the JSON Schema the client registers, generated from the zod definitions above. The write schemas carry the fields a reviewer needs — contactId and pitchId for send, fields for a CRM update — because an approval gate is only as useful as the context it shows a human.
{
"mention_search": {
"type": "object",
"properties": {
"query": { "type": "string" },
"since": { "type": "string" },
"limit": { "type": "integer" }
},
"required": ["query"],
"additionalProperties": false
},
"send_pitch": {
"type": "object",
"properties": {
"contactId": { "type": "string" },
"pitchId": { "type": "string" },
"scheduledAt": { "type": "string" }
},
"required": ["contactId", "pitchId"],
"additionalProperties": false
},
"update_contact": {
"type": "object",
"properties": {
"contactId": { "type": "string" },
"fields": {
"type": "object",
"additionalProperties": { "type": "string" }
}
},
"required": ["contactId", "fields"],
"additionalProperties": false
}
}
mcpServers config
Connect any MCP client with this block. The oauth entry requests the scope set at handshake; a monitoring-only agent should request only the read scopes and will then never be offered a write path it can exercise.
{
"mcpServers": {
"pr-media": {
"command": "node",
"args": ["dist/index.js"],
"env": {
"PR_AUTH_URL": "https://auth.example.com",
"PR_AUDIENCE": "api.pr.example.com",
"PR_LOG_LEVEL": "info"
},
"oauth": {
"authorization_url": "https://auth.example.com/authorize",
"token_url": "https://auth.example.com/oauth/token",
"scopes": ["mention:read", "sentiment:read", "pitch:read", "pitch:send", "contacts:write"],
"audience": "api.pr.example.com"
}
}
}
}
OAuth 2.0 security
The boundary is enforced through OAuth scopes, and it has to hold at three layers, not one. Authorization: the access token's aud claim must match api.pr.example.com, and the server rejects tokens minted for other APIs before dispatch even runs. Audience validation: the authorization server must issue tokens for the exact API audience the media tools call, so a token meant for a different SaaS cannot be replayed here. Scopes: the write dispatcher checks the token's scopes at every call — pitch:send in the config block means nothing if the token does not carry it. Credential isolation: the server holds the agent's token and the upstream PR SaaS service account separately, never forwarding one identity's credential to the other provider, and upstream calls use short-lived service tokens scoped to the narrowest operation. PKCE is required for public clients, and because this server can eventually touch external mailboxes, token revocation should be immediate and audit-logged.
Retry rules
Retry policy in this server is a correctness question, not just an availability one. Monitoring reads: retry up to three times with exponential backoff (base 200 ms, factor 2, jitter 25%) on 429 and 503; all 4xx are terminal. Pitch-queue reads are the same — they are cheap and read-only. Write-intent creation: one retry after one second on 5xx only; intents are idempotent via intent ID, so replays return the existing record rather than stacking a second proposal. Approval submission: zero automatic retries — a timeout leaves the intent pending and the account team reconciles state manually. Executing a send the gate already accepted: retry at most twice on 408 and 503, always replaying the same idempotency key so a timed-out pitch cannot double-send. Timeouts: reads at 10 seconds, intent staging at 15 seconds, execution at 30 seconds; anything over budget is logged as a high-severity audit event.
Frequently Asked Questions
What makes this server different from a plain media monitoring tool?
A monitoring dashboard shows a human mentions and sentiment. pr-media-mcp exposes those reads to agents AND gates the writes — sending a pitch or editing a contact — behind OAuth scopes and human approval, with every action in an audit trail. The monitoring becomes agentic without the agent ever acting unilaterally.
Why gate send_pitch behind human approval?
A pitch is an irreversible, externally visible, reputation-bearing message. Once it is in a journalist's inbox it cannot be recalled. The agent proposes, queues, and drafts; a human decides whether the pitch leaves the building. That is not bureaucracy, it is the difference between automation and risk.
How does journalist sentiment tracking work?
The sentiment tool reads recent coverage of a topic or client and classifies each mention into positive, neutral, or negative using the outlet, tone, and engagement signals the monitoring API returns. The result is a read-only trend an agent can summarize for account leadership. It never writes back to the monitoring feed.
Which scopes should a read-only analyst agent request?
Only the read scopes: mention:read, sentiment:read, and pitch:read. The server will serve every read it has and reject any write the token cannot support. A scope-lean token is the cheapest possible insurance against a stray write.
Where does the audit trail live?
In an append-only store that records every mention scan, every proposal, every blocked attempt, every approval decision, and every execution, with principal, tool, target, and outcome. If the trail cannot answer "who proposed, who approved, what changed," it is not an audit trail.
Closing thoughts
Featured's July 2026 MCP server gave the PR industry its first taste of agentic media monitoring, and the wave that followed proved the arithmetic: monitoring is where agents add value, and sending is where humans keep control. pr-media-mcp makes both statements structural. Read tools keep the always-on visibility layer; write tools live behind OAuth scopes and an approval gate; and an append-only audit trail sits under everything, so a communications team gets an agent that watches more, prepares more, and never once acts alone. Build the boundary first, and let the agents monitor the press.
Enjoyed this breakdown? Get our morning dispatch in your inbox.
Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.
Deepak Bagada
CEO, SaaSNext
Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.
Microsoft's Read-Write Agent Shift: When AI Tools Move from Reading to Acting
Next Story →Ahrefs Letaido: The Agent Workspace That Owns the Marketing Grind
Related Intelligence Analysis
Vercel AI SDK Tool Calling React: 5 Steps (2026)
Vercel AI SDK tool calling React integration is a programming pattern that executes server-side functions based on large language model decisions and streams the results to a React frontend. By combining streamText with...
Fact-Density vs. Word Count: The New SEO for 2026
Fact Density is the ratio of verifiable, unique information to the total word count of a piece of content. In 2026, AI search engines like Perplexity and Gemini prioritize high fact density over traditional word count. A...
NVIDIA Audex vs Qwen3.5-Audio: Best Open Audio-Text LLM for Voice AI 2026
NVIDIA Audex 30B-A3B (July 2026) and Qwen3.5-35B-A3B are the two leading open audio-text LLMs. Audex uniquely handles both audio understanding and generation in a single model while preserving text intelligence. Qwen3.5-...