@omnicensus/mcp-server 0.3.2

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.
Files changed (3) hide show
  1. package/README.md +85 -0
  2. package/dist/index.js +398 -0
  3. package/package.json +49 -0
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Omnicensus MCP Server
2
+
3
+ Score and analyze videos through any MCP-compatible AI agent — Claude Desktop, Cursor, Windsurf, Continue, Cline, and others.
4
+
5
+ ## What it does
6
+
7
+ Exposes 27 tools to the agent. The core ones:
8
+
9
+ | Tool | Use |
10
+ |---|---|
11
+ | `quick_score` | Score a video's hook in ~10s. No signup needed. Returns viral score + hook diagnostic. |
12
+ | `analyze_video` | Submit for full analysis (drop-off moments, edit suggestions, best 30s/60s clips, audience reactions). |
13
+ | `get_job_status` | Poll a submitted job. |
14
+ | `get_capabilities` | List analysis modes, platforms, current API version. |
15
+ | `get_agent_balance` | Check credit balance. |
16
+ | `submit_retention_feedback` | Send real view/retention numbers to improve accuracy. |
17
+ | `extract_clips` | Pull the best short clips from a video. |
18
+
19
+ Plus ~20 more for thumbnails, FCPXML/CSV exports, hook variants, report chat, channel insights, competitor teardown, audience exploration, social scheduling/posting, brain-map sharing, credit purchase, and team/org billing. Call `get_capabilities` for the live catalog.
20
+
21
+ ## Install (Claude Desktop)
22
+
23
+ ```bash
24
+ claude mcp add omnicensus npx -- -y @omnicensus/mcp-server
25
+ ```
26
+
27
+ Then in `~/Library/Application Support/Claude/claude_desktop_config.json` add your API key:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "omnicensus": {
33
+ "command": "npx",
34
+ "args": ["-y", "@omnicensus/mcp-server"],
35
+ "env": { "OMNICENSUS_API_KEY": "sk_live_..." }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Install (Cursor / Windsurf / Continue)
42
+
43
+ Add to your `~/.cursor/mcp.json` (Cursor) or equivalent:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "omnicensus": {
49
+ "command": "npx",
50
+ "args": ["-y", "@omnicensus/mcp-server"],
51
+ "env": { "OMNICENSUS_API_KEY": "sk_live_..." }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Get an API key
58
+
59
+ 1. Sign in at https://omnicensus.video
60
+ 2. Settings → Developers → Generate API key
61
+ 3. Copy the `sk_live_...` value into the env above
62
+
63
+ ## Example prompts (once installed)
64
+
65
+ > "Score this video for me: https://example.com/myvideo.mp4"
66
+
67
+ > "Analyze https://example.com/long-form.mp4 for TikTok — find me the best 30-second clip."
68
+
69
+ > "What's my Omnicensus balance?"
70
+
71
+ ## Local development
72
+
73
+ From the Omnicensus repo:
74
+
75
+ ```bash
76
+ cd mcp-server
77
+ npm install
78
+ npm run dev # runs from src/ via tsx
79
+ ```
80
+
81
+ To point at a local backend, set `OMNICENSUS_API_BASE` to your emulator's functions URL.
82
+
83
+ ## License
84
+
85
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Omnicensus MCP Server — full user-side coverage for AI agents.
4
+ *
5
+ * Auth: API key via env OMNICENSUS_API_KEY (get one at
6
+ * https://omnicensus.video/settings?tab=developers).
7
+ *
8
+ * Install (Claude Desktop):
9
+ * claude mcp add omnicensus npx -- -y @omnicensus/mcp-server
10
+ *
11
+ * Cursor / Windsurf: drop this in mcp.json:
12
+ * { "omnicensus": { "command": "npx", "args": ["-y", "@omnicensus/mcp-server"],
13
+ * "env": { "OMNICENSUS_API_KEY": "sk_live_..." } } }
14
+ */
15
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
+ import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
18
+ import { z } from "zod";
19
+ const API_BASE = process.env.OMNICENSUS_API_BASE
20
+ ?? "https://us-central1-tribemind-ac19c.cloudfunctions.net";
21
+ const API_KEY = process.env.OMNICENSUS_API_KEY ?? "";
22
+ function authHeaders() {
23
+ if (!API_KEY)
24
+ return { "Content-Type": "application/json" };
25
+ return { "Content-Type": "application/json", "x-api-key": API_KEY };
26
+ }
27
+ // raw=true returns the response body as a string (for non-JSON endpoints like
28
+ // exportJob, which returns an FCPXML document). All calls have a 60s timeout —
29
+ // every endpoint returns quickly (jobs return a job_id you then poll), so a hang
30
+ // means something is wrong and we surface a clear error instead of blocking.
31
+ async function call(path, init = {}, raw = false) {
32
+ const ctrl = new AbortController();
33
+ const timer = setTimeout(() => ctrl.abort(), 60_000);
34
+ let res;
35
+ try {
36
+ res = await fetch(`${API_BASE}${path}`, {
37
+ ...init,
38
+ headers: { ...authHeaders(), ...(init.headers || {}) },
39
+ signal: ctrl.signal,
40
+ });
41
+ }
42
+ catch (e) {
43
+ clearTimeout(timer);
44
+ if (e?.name === "AbortError")
45
+ throw new McpError(ErrorCode.InternalError, "Omnicensus request timed out after 60s");
46
+ throw new McpError(ErrorCode.InternalError, `Omnicensus network error: ${e?.message ?? String(e)}`);
47
+ }
48
+ clearTimeout(timer);
49
+ const text = await res.text();
50
+ if (!res.ok) {
51
+ let msg = text.slice(0, 200);
52
+ try {
53
+ const j = JSON.parse(text);
54
+ msg = j?.error?.message ?? j?.error ?? msg;
55
+ }
56
+ catch { /* non-JSON error body */ }
57
+ throw new McpError(ErrorCode.InternalError, `Omnicensus ${res.status}: ${msg}`);
58
+ }
59
+ if (raw)
60
+ return text;
61
+ try {
62
+ return JSON.parse(text);
63
+ }
64
+ catch {
65
+ return { raw: text };
66
+ }
67
+ }
68
+ // ── Tool schemas ────────────────────────────────────────────────────────────
69
+ const QuickScore = z.object({ video_url: z.string().url() });
70
+ const AnalyzeVideo = z.object({
71
+ video_url: z.string().url(),
72
+ analysis_mode: z.enum(["fast", "deep", "turbo"]).default("fast"),
73
+ platform: z.enum(["youtube", "youtube_shorts", "tiktok", "instagram_reels"]).optional(),
74
+ duration_minutes: z.number().min(0.1).max(360).optional(),
75
+ });
76
+ const GetJobStatus = z.object({
77
+ job_id: z.string(),
78
+ output_format: z.enum(["agent", "default"]).default("agent"),
79
+ });
80
+ const ListRecentJobs = z.object({ limit: z.number().min(1).max(100).default(20) });
81
+ const ShareReport = z.object({ job_id: z.string(), is_public: z.boolean().default(true) });
82
+ const ExportJob = z.object({
83
+ job_id: z.string(),
84
+ format: z.enum(["fcpxml"]).default("fcpxml"),
85
+ });
86
+ const SubmitFeedback = z.object({
87
+ job_id: z.string(),
88
+ actual_views: z.number().int().min(0).optional(),
89
+ actual_retention_rate: z.number().min(0).max(1).optional(),
90
+ feedback_text: z.string().max(2000).optional(),
91
+ });
92
+ const BuyCredits = z.object({});
93
+ const ManageBilling = z.object({});
94
+ const GetCapabilities = z.object({});
95
+ const GetAgentBalance = z.object({});
96
+ const CreateSubAgentKey = z.object({
97
+ name: z.string().min(1).max(60),
98
+ spend_cap_credits: z.number().int().min(1).max(10000).default(100),
99
+ });
100
+ const SocialPlatform = z.enum(["youtube_shorts", "tiktok", "instagram_reels"]);
101
+ const ExtractClips = z.object({
102
+ video_url: z.string().url(),
103
+ num_clips: z.number().int().min(1).max(5).default(5),
104
+ aspect_ratio: z.enum(["source", "9:16", "1:1", "16:9"]).default("source"),
105
+ caption_language: z.enum(["en", "es", "pt", "fr", "de", "hi", "zh", "ja", "ko", "ar"]).optional(),
106
+ include_broll: z.boolean().optional(),
107
+ });
108
+ const ScoreHookVariant = z.object({
109
+ job_id: z.string(),
110
+ hook_text: z.string().min(1).max(600),
111
+ });
112
+ const ChatWithReport = z.object({
113
+ job_id: z.string(),
114
+ messages: z.array(z.object({
115
+ role: z.enum(["user", "assistant"]),
116
+ content: z.string(),
117
+ })).min(1),
118
+ chat_id: z.string().optional(),
119
+ mode: z.enum(["report", "ideas"]).default("report"),
120
+ });
121
+ const ExploreAudience = z.object({
122
+ job_id: z.string(),
123
+ demographics: z.record(z.unknown()),
124
+ });
125
+ const PostClipToSocial = z.object({
126
+ job_id: z.string(),
127
+ platforms: z.array(SocialPlatform).min(1),
128
+ clip_index: z.number().int().min(0).optional(),
129
+ caption: z.string().optional(),
130
+ hashtags: z.array(z.string()).optional(),
131
+ });
132
+ const SchedulePost = z.object({
133
+ job_id: z.string(),
134
+ platforms: z.array(SocialPlatform).min(1),
135
+ scheduled_at: z.number().int(),
136
+ clip_type: z.string().default("full"),
137
+ caption: z.string().optional(),
138
+ hashtags: z.array(z.string()).max(30).optional(),
139
+ });
140
+ const CreateOrgJob = z.object({
141
+ org_id: z.string(),
142
+ client_id: z.string(),
143
+ video_url: z.string().url(),
144
+ analysis_mode: z.enum(["fast", "turbo", "deep"]).optional(),
145
+ });
146
+ const GetOrgRollup = z.object({ org_id: z.string() });
147
+ const GenerateThumbnails = z.object({
148
+ job_id: z.string(),
149
+ platforms: z.array(z.enum(["youtube", "short", "square"])).optional(),
150
+ k: z.number().int().min(1).max(5).optional(),
151
+ });
152
+ const ChannelInsights = z.object({
153
+ limit: z.number().int().min(3).max(50).optional(),
154
+ org_id: z.string().optional(),
155
+ client_id: z.string().optional(),
156
+ });
157
+ const CompetitorTeardown = z.object({ competitor_job_id: z.string() });
158
+ const ApplyEdits = z.object({ job_id: z.string() });
159
+ const ShareBrainMap = z.object({ job_id: z.string() });
160
+ const FundOrgPool = z.object({ org_id: z.string(), credits: z.number().int().min(5).max(5000) });
161
+ const SetAccountProfile = z.object({ account_type: z.enum(['individual', 'business']), company_name: z.string().optional(), company_size: z.string().optional(), website: z.string().optional(), vat_id: z.string().optional(), use_case: z.string().optional(), country: z.string().optional() });
162
+ // ── Server setup ────────────────────────────────────────────────────────────
163
+ const server = new Server({ name: "omnicensus-mcp", version: "0.2.0" }, { capabilities: { tools: {} } });
164
+ // One-sentence descriptions per tool — total tools/list ≈ 350 tokens.
165
+ const TOOLS = [
166
+ { name: "quick_score", schema: QuickScore,
167
+ desc: "Score a video's hook in ~10s — no API key needed. Pass a public https video URL; returns {job_id, status}. Poll get_job_status until status='completed'." },
168
+ { name: "analyze_video", schema: AnalyzeVideo,
169
+ desc: "Full engagement analysis (drop-offs, edit suggestions, best 30s/60s clips, audience reactions). Modes: fast (60s) | deep (5m) | turbo (15s, 1.5× credits). Returns {job_id}." },
170
+ { name: "get_job_status", schema: GetJobStatus,
171
+ desc: "Poll a job. output_format='agent' returns minimal report (saves ~1850 tokens). When status='completed', read report.agent_instructions.{verdict,edits,clips}. brain_simulation_url (when present) links the predicted Cortex brain-reaction map (literature-grounded, not a brain scan)." },
172
+ { name: "list_recent_jobs", schema: ListRecentJobs,
173
+ desc: "List the API key owner's recent jobs (default 20). Returns job_id, status, viral_score, share_url, created_at for each." },
174
+ { name: "share_report", schema: ShareReport,
175
+ desc: "Toggle a job's public visibility. is_public=true returns {share_url, og_image_url} the agent can post on social or hand back to the user." },
176
+ { name: "export_job", schema: ExportJob,
177
+ desc: "Export a job's edit suggestions as an FCPXML timeline file (imports into Final Cut Pro, Premiere, and DaVinci Resolve). Returns the FCPXML document as text." },
178
+ { name: "submit_retention_feedback", schema: SubmitFeedback,
179
+ desc: "After the user publishes the video, report back actual views / retention so future predictions improve. Free, improves model accuracy." },
180
+ { name: "buy_credits", schema: BuyCredits,
181
+ desc: "Get a Stripe Checkout URL (accepts card / Stripe Link / USDC crypto) so the human can buy 10 credits for $20. Return the URL to the user — agents never see card data." },
182
+ { name: "manage_billing", schema: ManageBilling,
183
+ desc: "Open Stripe Customer Portal — user can manage subscription, payment methods, view invoices. Returns a URL; hand it to the user." },
184
+ { name: "create_sub_agent_key", schema: CreateSubAgentKey,
185
+ desc: "Mint a scoped child API key with a credit spend cap (default 100). Use this to delegate to worker agents without sharing your master key." },
186
+ { name: "get_capabilities", schema: GetCapabilities,
187
+ desc: "One-shot discovery — returns the full Omnicensus tool catalog, modes, platforms, rate limits, share URL template, error codes. ~430 tokens." },
188
+ { name: "get_agent_balance", schema: GetAgentBalance,
189
+ desc: "Returns the API key's remaining credit balance. Call before buy_credits if you need to warn the user." },
190
+ { name: "extract_clips", schema: ExtractClips,
191
+ desc: "Render 1-5 short vertical clips from a video (1 credit/clip). aspect_ratio: source|9:16|1:1|16:9. Optional caption_language (translates burned captions) and include_broll (Pexels/Pixabay overlays). Applies the API key owner's saved brand kit. Returns {job_id}; poll get_job_status until ready, then read clips[].url." },
192
+ { name: "score_hook_variant", schema: ScoreHookVariant,
193
+ desc: "Predict a candidate hook's viral score (0-100) before reshooting. Free. Returns {predicted_score, current_score, delta, verdict, reasoning} for the given job_id." },
194
+ { name: "chat_with_report", schema: ChatWithReport,
195
+ desc: "Ask follow-up questions about an analyzed job's report (1 credit/message, refunded on failure). Pass the full messages[] history; returns {message, chat_id}. mode: report|ideas." },
196
+ { name: "explore_audience", schema: ExploreAudience,
197
+ desc: "Re-run analysis against a synthetic audience (free, max 10/hour). Pass demographics (ageGroups, gender, subscribedStatus, countries). Returns {exploration_id, status}." },
198
+ { name: "post_clip_to_social", schema: PostClipToSocial,
199
+ desc: "Post a rendered clip to connected accounts (youtube_shorts|tiktok|instagram_reels). Free. Account must already be connected in Settings. Returns per-platform {results, clip_url}." },
200
+ { name: "schedule_post", schema: SchedulePost,
201
+ desc: "Queue a future post to connected accounts. Free at schedule time; a cron publishes it when due. scheduled_at is a future unix timestamp in MILLISECONDS. Returns {id, status}." },
202
+ { name: "create_org_job", schema: CreateOrgJob,
203
+ desc: "Agency/team: analyze a video billed to an ORG credit pool (not personal). Your API key's owner must be a member (owner/admin/editor) of org_id. Deducts from the pool with per-member/client caps. Returns {job_id, credits_charged}; poll get_job_status." },
204
+ { name: "get_org_rollup", schema: GetOrgRollup,
205
+ desc: "Agency/team roll-up for org_id (member-only): org totals (pooled_credits, members, clients, jobs, spend) + per-client {jobs_count, avg_score, trend, recent}." },
206
+ { name: "generate_thumbnails", schema: GenerateThumbnails,
207
+ desc: "Generate ranked, platform-sized thumbnail candidates for an analyzed job. CV-scored frames (face/sharpness/contrast/colour/title-space) cropped to youtube(1280x720)/short(1080x1920)/square(1080x1080). k=1-5 (default 3). Free. Returns {status, job_id}; poll get_job_status for thumbnails.candidates[]." },
208
+ { name: "channel_insights", schema: ChannelInsights,
209
+ desc: "Aggregate patterns across recent jobs (default 20, max 50): avg score + trend, hook trend, common drop points (% of video), best-performing length bucket, recurring weak craft dimension, top genres. Personal by default; pass org_id (+optional client_id) for an agency scope (member-only). Free. Honest enough=false under 3 videos. Returns {insights, scope}." },
210
+ { name: "competitor_teardown", schema: CompetitorTeardown,
211
+ desc: "Diff an analysed competitor video against YOUR channel averages (analyze_video the competitor's URL first, then pass its job_id). Returns {teardown:{headline,they_win[],you_win[],parity[],competitor_score,your_avg_score}} — what they do better, where you lead, and parity. Only compares dimensions both sides have. Free. Honest enough=false with no own history." },
212
+ { name: "apply_edits", schema: ApplyEdits,
213
+ desc: "Render a downloadable edited cut of an analysed job by removing its 'cut' edit_suggestions (dead zones / low-energy spans). Free. Returns {status, job_id}; poll get_job_status for edited_cut{url, original_duration, edited_duration, removed_seconds, cuts_applied[]}." },
214
+ { name: "share_brain_map", schema: ShareBrainMap,
215
+ desc: "Make a job's predicted brain-reaction map public on a token-gated /brain/{token} page (honest 'predicted, not a scan' label). Job must have a brain_simulation_url. Free. Returns {share_url, token}." },
216
+ { name: "set_account_profile", schema: SetAccountProfile,
217
+ desc: "Set the account type (individual or business/corporate) + profile. A 'business' account unlocks the org/team workflow (create_org_job, get_org_rollup, fund_org_pool). Returns {ok, account_profile}." },
218
+ { name: "fund_org_pool", schema: FundOrgPool,
219
+ desc: "Fund an agency org credit pool by CARD via Stripe (owner/admin of org_id only). credits 5-5000 at $2/credit. LIVE BILLING — returns {url} (a Stripe checkout link); the pool is credited after the human completes payment. First top-up saves the card so the pool can auto-recharge." },
220
+ ];
221
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
222
+ tools: TOOLS.map(t => ({ name: t.name, description: t.desc, inputSchema: zToJsonSchema(t.schema) })),
223
+ }));
224
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
225
+ const name = req.params.name;
226
+ const args = req.params.arguments ?? {};
227
+ try {
228
+ switch (name) {
229
+ case "quick_score": {
230
+ const p = QuickScore.parse(args);
231
+ return toolResult(await call("/quickScore", { method: "POST", body: JSON.stringify({ video_url: p.video_url }) }));
232
+ }
233
+ case "analyze_video": {
234
+ const p = AnalyzeVideo.parse(args);
235
+ return toolResult(await call("/analyzeVideo", { method: "POST", body: JSON.stringify(p) }));
236
+ }
237
+ case "get_job_status": {
238
+ const p = GetJobStatus.parse(args);
239
+ return toolResult(await call(`/getJobStatus?job_id=${encodeURIComponent(p.job_id)}&output_format=${p.output_format}`));
240
+ }
241
+ case "list_recent_jobs": {
242
+ const p = ListRecentJobs.parse(args);
243
+ return toolResult(await call(`/listRecentJobs?limit=${p.limit}`));
244
+ }
245
+ case "share_report": {
246
+ const p = ShareReport.parse(args);
247
+ return toolResult(await call("/setJobVisibilityHttp", { method: "POST", body: JSON.stringify(p) }));
248
+ }
249
+ case "export_job": {
250
+ const p = ExportJob.parse(args);
251
+ // exportJob returns a raw FCPXML document, not JSON — return it as text.
252
+ const xml = await call(`/exportJob?job_id=${encodeURIComponent(p.job_id)}&format=${p.format}`, {}, true);
253
+ return { content: [{ type: "text", text: String(xml) }] };
254
+ }
255
+ case "submit_retention_feedback": {
256
+ const p = SubmitFeedback.parse(args);
257
+ return toolResult(await call("/ingestFeedback", { method: "POST", body: JSON.stringify(p) }));
258
+ }
259
+ case "buy_credits":
260
+ return toolResult(await call("/buyCreditsHttp", { method: "POST", body: "{}" }));
261
+ case "manage_billing":
262
+ return toolResult(await call("/manageBillingHttp", { method: "POST", body: "{}" }));
263
+ case "create_sub_agent_key": {
264
+ const p = CreateSubAgentKey.parse(args);
265
+ return toolResult(await call("/createSubAgentKey", { method: "POST", body: JSON.stringify(p) }));
266
+ }
267
+ case "get_capabilities":
268
+ return toolResult(await call("/getCapabilities"));
269
+ case "get_agent_balance":
270
+ return toolResult(await call("/getAgentBalance"));
271
+ case "extract_clips": {
272
+ const p = ExtractClips.parse(args);
273
+ return toolResult(await call("/extractClips", { method: "POST", body: JSON.stringify(p) }));
274
+ }
275
+ case "score_hook_variant": {
276
+ const p = ScoreHookVariant.parse(args);
277
+ return toolResult(await call("/scoreHookVariantHttp", { method: "POST", body: JSON.stringify(p) }));
278
+ }
279
+ case "chat_with_report": {
280
+ const p = ChatWithReport.parse(args);
281
+ return toolResult(await call("/chatWithReportHttp", { method: "POST", body: JSON.stringify(p) }));
282
+ }
283
+ case "explore_audience": {
284
+ const p = ExploreAudience.parse(args);
285
+ return toolResult(await call("/exploreAudienceHttp", { method: "POST", body: JSON.stringify(p) }));
286
+ }
287
+ case "post_clip_to_social": {
288
+ const p = PostClipToSocial.parse(args);
289
+ return toolResult(await call("/postClipToSocialHttp", { method: "POST", body: JSON.stringify(p) }));
290
+ }
291
+ case "schedule_post": {
292
+ const p = SchedulePost.parse(args);
293
+ return toolResult(await call("/schedulePostHttp", { method: "POST", body: JSON.stringify(p) }));
294
+ }
295
+ case "create_org_job": {
296
+ const p = CreateOrgJob.parse(args);
297
+ return toolResult(await call("/createOrgJobHttp", { method: "POST", body: JSON.stringify(p) }));
298
+ }
299
+ case "get_org_rollup": {
300
+ const p = GetOrgRollup.parse(args);
301
+ return toolResult(await call(`/getOrgRollupHttp?org_id=${encodeURIComponent(p.org_id)}`));
302
+ }
303
+ case "generate_thumbnails": {
304
+ const p = GenerateThumbnails.parse(args);
305
+ return toolResult(await call("/generateThumbnailsHttp", { method: "POST", body: JSON.stringify(p) }));
306
+ }
307
+ case "channel_insights": {
308
+ const p = ChannelInsights.parse(args);
309
+ const qs = new URLSearchParams();
310
+ if (p.limit)
311
+ qs.set("limit", String(p.limit));
312
+ if (p.org_id)
313
+ qs.set("org_id", p.org_id);
314
+ if (p.client_id)
315
+ qs.set("client_id", p.client_id);
316
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
317
+ return toolResult(await call(`/getChannelInsightsHttp${suffix}`));
318
+ }
319
+ case "competitor_teardown": {
320
+ const p = CompetitorTeardown.parse(args);
321
+ return toolResult(await call(`/getCompetitorTeardownHttp?competitor_job_id=${encodeURIComponent(p.competitor_job_id)}`));
322
+ }
323
+ case "apply_edits": {
324
+ const p = ApplyEdits.parse(args);
325
+ return toolResult(await call("/applyEditsHttp", { method: "POST", body: JSON.stringify(p) }));
326
+ }
327
+ case "share_brain_map": {
328
+ const p = ShareBrainMap.parse(args);
329
+ return toolResult(await call("/shareBrainMapHttp", { method: "POST", body: JSON.stringify(p) }));
330
+ }
331
+ case "set_account_profile": {
332
+ const p = SetAccountProfile.parse(args);
333
+ return toolResult(await call("/setAccountProfileHttp", { method: "POST", body: JSON.stringify(p) }));
334
+ }
335
+ case "fund_org_pool": {
336
+ const p = FundOrgPool.parse(args);
337
+ return toolResult(await call("/createOrgPoolCheckoutHttp", { method: "POST", body: JSON.stringify(p) }));
338
+ }
339
+ default:
340
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
341
+ }
342
+ }
343
+ catch (err) {
344
+ if (err instanceof z.ZodError) {
345
+ throw new McpError(ErrorCode.InvalidParams, `Invalid arguments: ${err.errors.map(e => `${e.path.join(".")}: ${e.message}`).join("; ")}`);
346
+ }
347
+ if (err instanceof McpError)
348
+ throw err;
349
+ throw new McpError(ErrorCode.InternalError, err instanceof Error ? err.message : String(err));
350
+ }
351
+ });
352
+ function toolResult(data) {
353
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
354
+ }
355
+ // Minimal zod → JSON Schema (covers ZodObject, ZodString, ZodNumber, ZodBoolean,
356
+ // ZodEnum, ZodArray, ZodRecord, ZodOptional, ZodDefault). Tight on purpose to
357
+ // keep tools/list small.
358
+ function zToJsonSchema(schema) {
359
+ const def = schema._def;
360
+ if (def.typeName === "ZodObject") {
361
+ const shape = def.shape();
362
+ const properties = {};
363
+ const required = [];
364
+ for (const [key, val] of Object.entries(shape)) {
365
+ const v = val;
366
+ properties[key] = zToJsonSchema(v);
367
+ if (!(v._def?.typeName === "ZodOptional" || v._def?.typeName === "ZodDefault")) {
368
+ required.push(key);
369
+ }
370
+ }
371
+ return { type: "object", properties, required };
372
+ }
373
+ if (def.typeName === "ZodOptional" || def.typeName === "ZodDefault")
374
+ return zToJsonSchema(def.innerType);
375
+ if (def.typeName === "ZodString")
376
+ return { type: "string" };
377
+ if (def.typeName === "ZodNumber")
378
+ return { type: "number" };
379
+ if (def.typeName === "ZodBoolean")
380
+ return { type: "boolean" };
381
+ if (def.typeName === "ZodEnum")
382
+ return { type: "string", enum: def.values };
383
+ if (def.typeName === "ZodArray")
384
+ return { type: "array", items: zToJsonSchema(def.type) };
385
+ if (def.typeName === "ZodRecord")
386
+ return { type: "object" };
387
+ return { type: "object" };
388
+ }
389
+ async function main() {
390
+ const transport = new StdioServerTransport();
391
+ await server.connect(transport);
392
+ process.stderr.write("[omnicensus-mcp] v0.2.0 connected via stdio\n");
393
+ }
394
+ main().catch((err) => {
395
+ process.stderr.write(`[omnicensus-mcp] fatal: ${err}\n`);
396
+ process.exit(1);
397
+ });
398
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@omnicensus/mcp-server",
3
+ "version": "0.3.2",
4
+ "description": "MCP server for Omnicensus — let Claude, Cursor, Windsurf, and any MCP-compatible AI agent score, analyze, share, export, and bill on the Omnicensus video-engagement platform.",
5
+ "license": "MIT",
6
+ "homepage": "https://omnicensus.video",
7
+ "bin": {
8
+ "omnicensus-mcp": "dist/index.js"
9
+ },
10
+ "main": "dist/index.js",
11
+ "type": "module",
12
+ "files": [
13
+ "dist/index.js",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "start": "node dist/index.js",
19
+ "dev": "tsx src/index.ts",
20
+ "test": "node test-smoke.mjs",
21
+ "prepublishOnly": "npm run build"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "dependencies": {
30
+ "@modelcontextprotocol/sdk": "^1.0.0",
31
+ "zod": "^3.23.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^22.0.0",
35
+ "tsx": "^4.19.0",
36
+ "typescript": "^5.6.0"
37
+ },
38
+ "keywords": [
39
+ "mcp",
40
+ "modelcontextprotocol",
41
+ "claude",
42
+ "cursor",
43
+ "windsurf",
44
+ "video",
45
+ "engagement",
46
+ "ai-agent",
47
+ "omnicensus"
48
+ ]
49
+ }