gen-mcp-server 0.1.0__tar.gz
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.
- gen_mcp_server-0.1.0/.gitignore +7 -0
- gen_mcp_server-0.1.0/PKG-INFO +53 -0
- gen_mcp_server-0.1.0/README.md +35 -0
- gen_mcp_server-0.1.0/pyproject.toml +34 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/__init__.py +3 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/clients/__init__.py +4 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/clients/client.py +101 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/clients/errors.py +118 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/prompts/__init__.py +142 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/resources/__init__.py +5 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/resources/guidance.py +135 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/server.py +27 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/__init__.py +32 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/_helpers.py +32 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/assets.py +51 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/billing.py +76 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/compose.py +67 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/generation.py +119 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/recurring.py +81 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/schedule.py +72 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/setup.py +60 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/templates.py +33 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/vidsheet.py +101 -0
- gen_mcp_server-0.1.0/src/gen_mcp_server/tools/voice.py +84 -0
- gen_mcp_server-0.1.0/tests/test_errors.py +47 -0
- gen_mcp_server-0.1.0/tests/test_server.py +43 -0
- gen_mcp_server-0.1.0/tests/test_tool_guard.py +40 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gen-mcp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: GEN MCP server — exposes the GEN platform (Auto Content Engine + agent chat) to MCP clients like Claude Code, Cursor, and VS Code.
|
|
5
|
+
Project-URL: Homepage, https://api.gen.pro
|
|
6
|
+
Project-URL: Repository, https://github.com/poweredbyGEN/gen-agentic
|
|
7
|
+
Author: GEN
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: ai,content,gen,gen-pro,mcp,video
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
12
|
+
Requires-Dist: httpx>=0.27.0
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# gen-mcp-server
|
|
20
|
+
|
|
21
|
+
MCP server for the **GEN** platform — exposes the GEN Auto Content Engine + agent
|
|
22
|
+
chat to MCP clients (Claude Code, Cursor, VS Code).
|
|
23
|
+
|
|
24
|
+
It mirrors what a user can do through the GEN front end: set up agents, build
|
|
25
|
+
vidsheets, generate images (incl. Nano Banana Pro) and video, voice, render,
|
|
26
|
+
schedule/publish, manage credits, and run daily tasks. The `gen_do` tool
|
|
27
|
+
delegates natural-language goals to the GEN composer for multi-step outcomes.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install gen-mcp-server # or: uvx gen-mcp-server
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configure (Claude Code)
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
claude mcp add gen --env GEN_API_KEY=your-pat -- gen-mcp-server
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Get a Personal Access Token at https://gen.pro (log in → pick an agent → API page).
|
|
42
|
+
|
|
43
|
+
## Environment
|
|
44
|
+
|
|
45
|
+
- `GEN_API_KEY` (required) — your Personal Access Token
|
|
46
|
+
- `GEN_API_BASE_URL` (default `https://api.gen.pro/v1`)
|
|
47
|
+
- `GEN_AGENT_API_URL` (default `https://agent.gen.pro/v1`)
|
|
48
|
+
- `GEN_AGENT_CORE_API_URL` (default `https://agent-core.gen.pro/v1`)
|
|
49
|
+
|
|
50
|
+
## Errors & credits
|
|
51
|
+
|
|
52
|
+
Backend errors are surfaced cleanly. If you run out of credits, tools return
|
|
53
|
+
`insufficient_credits: true` with a message pointing to `gen_buy_credits`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# gen-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP server for the **GEN** platform — exposes the GEN Auto Content Engine + agent
|
|
4
|
+
chat to MCP clients (Claude Code, Cursor, VS Code).
|
|
5
|
+
|
|
6
|
+
It mirrors what a user can do through the GEN front end: set up agents, build
|
|
7
|
+
vidsheets, generate images (incl. Nano Banana Pro) and video, voice, render,
|
|
8
|
+
schedule/publish, manage credits, and run daily tasks. The `gen_do` tool
|
|
9
|
+
delegates natural-language goals to the GEN composer for multi-step outcomes.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install gen-mcp-server # or: uvx gen-mcp-server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configure (Claude Code)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
claude mcp add gen --env GEN_API_KEY=your-pat -- gen-mcp-server
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Get a Personal Access Token at https://gen.pro (log in → pick an agent → API page).
|
|
24
|
+
|
|
25
|
+
## Environment
|
|
26
|
+
|
|
27
|
+
- `GEN_API_KEY` (required) — your Personal Access Token
|
|
28
|
+
- `GEN_API_BASE_URL` (default `https://api.gen.pro/v1`)
|
|
29
|
+
- `GEN_AGENT_API_URL` (default `https://agent.gen.pro/v1`)
|
|
30
|
+
- `GEN_AGENT_CORE_API_URL` (default `https://agent-core.gen.pro/v1`)
|
|
31
|
+
|
|
32
|
+
## Errors & credits
|
|
33
|
+
|
|
34
|
+
Backend errors are surfaced cleanly. If you run out of credits, tools return
|
|
35
|
+
`insufficient_credits: true` with a message pointing to `gen_buy_credits`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gen-mcp-server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "GEN MCP server — exposes the GEN platform (Auto Content Engine + agent chat) to MCP clients like Claude Code, Cursor, and VS Code."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "GEN" }]
|
|
9
|
+
keywords = ["mcp", "gen", "gen-pro", "ai", "video", "content"]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"fastmcp>=2.0.0",
|
|
12
|
+
"httpx>=0.27.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.23", "respx>=0.21"]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
gen-mcp-server = "gen_mcp_server.server:main"
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://api.gen.pro"
|
|
23
|
+
Repository = "https://github.com/poweredbyGEN/gen-agentic"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["hatchling"]
|
|
27
|
+
build-backend = "hatchling.build"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["src/gen_mcp_server"]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
asyncio_mode = "auto"
|
|
34
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""HTTP clients for the three GEN backends.
|
|
2
|
+
|
|
3
|
+
The MCP talks to three base URLs (same split the front end uses):
|
|
4
|
+
* api.gen.pro — Rails: content engine, generations, assets, billing
|
|
5
|
+
* agent.gen.pro — gen-agentic: chat/run, ideas, recurring jobs, schedule
|
|
6
|
+
* agent-core.gen.pro — agent-core: brand/onboarding, watchlists
|
|
7
|
+
|
|
8
|
+
Every outbound request carries:
|
|
9
|
+
* ``X-API-Key`` — the user's PAT (from GEN_API_KEY)
|
|
10
|
+
* ``X-Client`` — usage attribution so existing GEN dashboards can filter MCP
|
|
11
|
+
traffic (no new analytics system needed)
|
|
12
|
+
|
|
13
|
+
Failures are normalized via :func:`normalize_error` into ``GenApiError`` so the
|
|
14
|
+
MCP surfaces backend errors (especially out-of-credits) exactly like the FE.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import httpx
|
|
23
|
+
|
|
24
|
+
from .errors import GenApiError, normalize_error
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
CLIENT_TAG = f"gen-mcp-server/{__version__}"
|
|
28
|
+
|
|
29
|
+
API_BASE = os.environ.get("GEN_API_BASE_URL", "https://api.gen.pro/v1")
|
|
30
|
+
AGENT_BASE = os.environ.get("GEN_AGENT_API_URL", "https://agent.gen.pro/v1")
|
|
31
|
+
AGENT_CORE_BASE = os.environ.get("GEN_AGENT_CORE_API_URL", "https://agent-core.gen.pro/v1")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _api_key() -> str:
|
|
35
|
+
key = os.environ.get("GEN_API_KEY")
|
|
36
|
+
if not key:
|
|
37
|
+
raise GenApiError(
|
|
38
|
+
"GEN_API_KEY is not set. Create a Personal Access Token at "
|
|
39
|
+
"https://gen.pro (log in → pick an agent → API page) and set it as "
|
|
40
|
+
"the GEN_API_KEY environment variable.",
|
|
41
|
+
error_code="missing_api_key",
|
|
42
|
+
)
|
|
43
|
+
return key
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class GenClient:
|
|
47
|
+
"""A thin async HTTP client for one GEN backend base URL."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, base_url: str) -> None:
|
|
50
|
+
self.base_url = base_url
|
|
51
|
+
|
|
52
|
+
async def request(
|
|
53
|
+
self,
|
|
54
|
+
method: str,
|
|
55
|
+
path: str,
|
|
56
|
+
*,
|
|
57
|
+
params: dict[str, Any] | None = None,
|
|
58
|
+
json: Any | None = None,
|
|
59
|
+
data: Any | None = None,
|
|
60
|
+
) -> Any:
|
|
61
|
+
headers = {
|
|
62
|
+
"X-API-Key": _api_key(),
|
|
63
|
+
"X-Client": CLIENT_TAG,
|
|
64
|
+
"Accept": "application/json",
|
|
65
|
+
}
|
|
66
|
+
url = f"{self.base_url}{path}"
|
|
67
|
+
try:
|
|
68
|
+
async with httpx.AsyncClient(timeout=120) as http:
|
|
69
|
+
resp = await http.request(
|
|
70
|
+
method, url, params=params, json=json, data=data, headers=headers
|
|
71
|
+
)
|
|
72
|
+
except httpx.TimeoutException as exc:
|
|
73
|
+
raise GenApiError(
|
|
74
|
+
"The GEN service took too long to respond. Try again.",
|
|
75
|
+
error_code="timeout",
|
|
76
|
+
) from exc
|
|
77
|
+
except httpx.HTTPError as exc:
|
|
78
|
+
raise GenApiError(
|
|
79
|
+
"Could not reach the GEN service.", error_code="network_error"
|
|
80
|
+
) from exc
|
|
81
|
+
|
|
82
|
+
if resp.is_success:
|
|
83
|
+
if not resp.content:
|
|
84
|
+
return {"ok": True}
|
|
85
|
+
try:
|
|
86
|
+
return resp.json()
|
|
87
|
+
except ValueError:
|
|
88
|
+
return {"ok": True, "raw": resp.text}
|
|
89
|
+
|
|
90
|
+
# Failure → normalize to a user-facing error (handles out-of-credits).
|
|
91
|
+
try:
|
|
92
|
+
body: Any = resp.json()
|
|
93
|
+
except ValueError:
|
|
94
|
+
body = resp.text
|
|
95
|
+
raise normalize_error(resp.status_code, body)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Singletons for the three backends.
|
|
99
|
+
api = GenClient(API_BASE)
|
|
100
|
+
agent = GenClient(AGENT_BASE)
|
|
101
|
+
agent_core = GenClient(AGENT_CORE_BASE)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Error normalization for the GEN MCP server.
|
|
2
|
+
|
|
3
|
+
The MCP must surface backend errors to the user EXACTLY like the GEN backend
|
|
4
|
+
tells the front end. The backend returns a standard shape on failure:
|
|
5
|
+
|
|
6
|
+
{"error": "<human message>", "error_code": "<machine_code>"}
|
|
7
|
+
|
|
8
|
+
This module turns any HTTP failure into a clean, user-facing ``GenApiError``
|
|
9
|
+
with a friendly message. Out-of-credits is treated specially: it is the most
|
|
10
|
+
common paid-operation failure and the user should be told plainly that they are
|
|
11
|
+
out of credits (and how to buy more), never shown a raw 422/500.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
# Backend error_codes that mean "not enough credits to run this paid operation".
|
|
19
|
+
INSUFFICIENT_CREDITS_CODES = {
|
|
20
|
+
"insufficient_credits_for_job",
|
|
21
|
+
"insufficient_credits",
|
|
22
|
+
"not_enough_credits",
|
|
23
|
+
"insufficient_generic_credits",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GenApiError(Exception):
|
|
28
|
+
"""A normalized, user-facing GEN API error.
|
|
29
|
+
|
|
30
|
+
Attributes
|
|
31
|
+
----------
|
|
32
|
+
message: friendly message to show the user
|
|
33
|
+
error_code: the backend machine code (may be None for transport errors)
|
|
34
|
+
status: HTTP status code (may be None)
|
|
35
|
+
insufficient_credits: True when this is an out-of-credits failure
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
message: str,
|
|
41
|
+
*,
|
|
42
|
+
error_code: str | None = None,
|
|
43
|
+
status: int | None = None,
|
|
44
|
+
insufficient_credits: bool = False,
|
|
45
|
+
) -> None:
|
|
46
|
+
super().__init__(message)
|
|
47
|
+
self.message = message
|
|
48
|
+
self.error_code = error_code
|
|
49
|
+
self.status = status
|
|
50
|
+
self.insufficient_credits = insufficient_credits
|
|
51
|
+
|
|
52
|
+
def to_user_text(self) -> str:
|
|
53
|
+
"""Render the error the way it should appear to the user via MCP."""
|
|
54
|
+
if self.insufficient_credits:
|
|
55
|
+
return (
|
|
56
|
+
"⚠️ You're out of credits for this operation. "
|
|
57
|
+
"Top up with the `gen_buy_credits` tool (or upgrade your plan with "
|
|
58
|
+
"`gen_upgrade_workspace`), then try again."
|
|
59
|
+
)
|
|
60
|
+
code = f" [{self.error_code}]" if self.error_code else ""
|
|
61
|
+
return f"❌ {self.message}{code}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def normalize_error(status: int, body: Any) -> GenApiError:
|
|
65
|
+
"""Map a failed HTTP response into a GenApiError.
|
|
66
|
+
|
|
67
|
+
``body`` is whatever the response carried — ideally the standard
|
|
68
|
+
``{"error", "error_code"}`` dict, but we defend against html/plain/empty.
|
|
69
|
+
"""
|
|
70
|
+
error_code: str | None = None
|
|
71
|
+
message: str | None = None
|
|
72
|
+
|
|
73
|
+
if isinstance(body, dict):
|
|
74
|
+
error_code = body.get("error_code") or body.get("code")
|
|
75
|
+
# FastAPI validation errors come back as {"detail": [...]} — handle first.
|
|
76
|
+
detail = body.get("detail")
|
|
77
|
+
if isinstance(detail, list):
|
|
78
|
+
message = (
|
|
79
|
+
"; ".join(str(d.get("msg", d)) if isinstance(d, dict) else str(d) for d in detail)
|
|
80
|
+
or "Validation error"
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
message = (
|
|
84
|
+
body.get("error")
|
|
85
|
+
or body.get("message")
|
|
86
|
+
or (detail if isinstance(detail, str) else None)
|
|
87
|
+
)
|
|
88
|
+
elif isinstance(body, str) and body.strip():
|
|
89
|
+
message = body.strip()[:500]
|
|
90
|
+
|
|
91
|
+
insufficient = bool(error_code and error_code in INSUFFICIENT_CREDITS_CODES)
|
|
92
|
+
# Some backends only flag credits via 402 Payment Required.
|
|
93
|
+
if status == 402:
|
|
94
|
+
insufficient = True
|
|
95
|
+
|
|
96
|
+
if message is None:
|
|
97
|
+
message = _default_message_for_status(status)
|
|
98
|
+
|
|
99
|
+
return GenApiError(
|
|
100
|
+
message,
|
|
101
|
+
error_code=error_code,
|
|
102
|
+
status=status,
|
|
103
|
+
insufficient_credits=insufficient,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _default_message_for_status(status: int) -> str:
|
|
108
|
+
return {
|
|
109
|
+
400: "The request was invalid.",
|
|
110
|
+
401: "Authentication failed — check your GEN_API_KEY.",
|
|
111
|
+
403: "You don't have permission to do that.",
|
|
112
|
+
404: "Not found.",
|
|
113
|
+
422: "The request could not be processed.",
|
|
114
|
+
429: "Rate limited — please slow down and retry.",
|
|
115
|
+
500: "The GEN service hit an internal error.",
|
|
116
|
+
502: "The GEN service is temporarily unavailable.",
|
|
117
|
+
503: "The GEN service is temporarily unavailable.",
|
|
118
|
+
}.get(status, f"Request failed (HTTP {status}).")
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""MCP Prompts — the GEN home-screen Plays.
|
|
2
|
+
|
|
3
|
+
These are the 9 published default Plays shown on the per-agent home screen
|
|
4
|
+
(``src/gen/plays/registry.py::DEFAULT_PLAYS``). Exposing them as MCP prompts
|
|
5
|
+
gives MCP clients the same vetted, high-value starting points users get in the
|
|
6
|
+
product. Each prompt returns the Play's seed instruction; the assistant runs it
|
|
7
|
+
(via gen_do delegation or the deterministic tools).
|
|
8
|
+
|
|
9
|
+
This module currently mirrors the registry statically. The follow-on phase
|
|
10
|
+
derives these from the live ``shortcut_registry`` so new/edited Plays appear as
|
|
11
|
+
MCP prompts automatically (and only gate-passed Plays are surfaced).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
# Mirror of the 9 published default Plays (registry.py DEFAULT_PLAYS),
|
|
19
|
+
# grouped: make_content / spot_trends / know_audience.
|
|
20
|
+
DEFAULT_PLAYS: list[dict[str, str]] = [
|
|
21
|
+
{
|
|
22
|
+
"name": "monitor_and_create_daily",
|
|
23
|
+
"group": "make_content",
|
|
24
|
+
"title": "Monitor a topic/account and create videos every day",
|
|
25
|
+
"prompt": (
|
|
26
|
+
"I want to automatically monitor a topic or account and have videos "
|
|
27
|
+
"created for me in my formats every day. Help me set this up: ask me "
|
|
28
|
+
"what to monitor, which video formats, and how often, explain what the "
|
|
29
|
+
"daily automation will do, and walk me through turning it into a "
|
|
30
|
+
"recurring Task. Use multi-step orchestration so it can monitor then "
|
|
31
|
+
"create then schedule in one run once it's set up. Before creating the "
|
|
32
|
+
"Task, summarize the exact details — what's monitored, formats, cadence, "
|
|
33
|
+
"delivery — and ask me to confirm; only create it after I confirm."
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "watchlist_and_weekly_report",
|
|
38
|
+
"group": "spot_trends",
|
|
39
|
+
"title": "Create a watchlist and send me a weekly report",
|
|
40
|
+
"prompt": (
|
|
41
|
+
"I want a watchlist of the topics or accounts I care about and a weekly "
|
|
42
|
+
"report emailed to me. Help me set this up: ask me what to track, confirm "
|
|
43
|
+
"the watchlist contents and the weekly report details, and only create "
|
|
44
|
+
"the watchlist and the recurring weekly Task after I confirm."
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "top_videos_in_my_niche",
|
|
49
|
+
"group": "spot_trends",
|
|
50
|
+
"title": "Top-performing videos in my niche, ranked by views last month",
|
|
51
|
+
"prompt": (
|
|
52
|
+
"Find the top-performing videos in my niche over the last month, ranked "
|
|
53
|
+
"by view count. Return the top ~20 with creator, views, hook, and format; "
|
|
54
|
+
"group them into the 3-4 themes that dominate; and tell me which theme I "
|
|
55
|
+
"should make a video about first. If you can only pull N, say so — no "
|
|
56
|
+
"silent caps."
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "competitor_sentiment",
|
|
61
|
+
"group": "know_audience",
|
|
62
|
+
"title": "Positive and negative sentiment about my competitors",
|
|
63
|
+
"prompt": (
|
|
64
|
+
"Pull recent comments across my competitors and classify sentiment "
|
|
65
|
+
"positive / negative / neutral. Summarize what people praise vs criticize "
|
|
66
|
+
"for each, with representative quotes, and flag the biggest opening for "
|
|
67
|
+
"me. Disclose how many comments you analyzed."
|
|
68
|
+
),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "my_account_hooks_and_views",
|
|
72
|
+
"group": "make_content",
|
|
73
|
+
"title": "Analyze my account's top videos by hooks and views",
|
|
74
|
+
"prompt": (
|
|
75
|
+
"Look at my account's best-performing recent videos by views. Break down "
|
|
76
|
+
"the hooks and formats that overperform my own average, what they have in "
|
|
77
|
+
"common, and 3 concrete things to repeat. If I haven't connected an "
|
|
78
|
+
"account, ask for my handle first."
|
|
79
|
+
),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "five_content_ideas",
|
|
83
|
+
"group": "make_content",
|
|
84
|
+
"title": "Give me 5 content ideas for my niche",
|
|
85
|
+
"prompt": (
|
|
86
|
+
"Give me 5 content ideas for my niche tuned to what's working right now — "
|
|
87
|
+
"each with a hook, format, and why it'll land. If you don't know my niche "
|
|
88
|
+
"yet, ask me first, then generate."
|
|
89
|
+
),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "ideas_from_a_video",
|
|
93
|
+
"group": "make_content",
|
|
94
|
+
"title": "Content ideas based on a specific video",
|
|
95
|
+
"prompt": (
|
|
96
|
+
"I want content ideas based on a specific video. Ask me for the link, "
|
|
97
|
+
"analyze its hook, format, and structure, then give me 5 ideas that adapt "
|
|
98
|
+
"what works to my brand."
|
|
99
|
+
),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "topic_comment_sentiment",
|
|
103
|
+
"group": "know_audience",
|
|
104
|
+
"title": "Analyze positive and negative comments for a topic",
|
|
105
|
+
"prompt": (
|
|
106
|
+
"Analyze positive and negative comments for a topic. Ask me for the topic "
|
|
107
|
+
"if I haven't given one, then pull recent comments, classify sentiment, "
|
|
108
|
+
"cluster the themes, and surface the strongest content angle. Disclose the "
|
|
109
|
+
"sample size."
|
|
110
|
+
),
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "video_templates",
|
|
114
|
+
"group": "make_content",
|
|
115
|
+
"title": "Show me templates for creating videos",
|
|
116
|
+
"prompt": (
|
|
117
|
+
"Show me video templates matched to the formats I make. List the available "
|
|
118
|
+
"templates with what each is good for, and offer to clone one into a new "
|
|
119
|
+
"vidsheet so I can go from idea to finished video fast."
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def register_all(mcp: Any) -> None:
|
|
126
|
+
def _make(play: dict[str, str]):
|
|
127
|
+
seed = play["prompt"]
|
|
128
|
+
|
|
129
|
+
def _prompt(agent_id: str) -> str:
|
|
130
|
+
return (
|
|
131
|
+
f"[GEN Play — {play['title']}] (agent {agent_id})\n\n{seed}\n\n"
|
|
132
|
+
"Use gen_do to run this end-to-end, or the deterministic gen_* tools "
|
|
133
|
+
"for exact steps. If a paid step reports insufficient_credits, tell me "
|
|
134
|
+
"I'm out of credits and offer gen_buy_credits."
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
_prompt.__name__ = play["name"]
|
|
138
|
+
_prompt.__doc__ = f"GEN Play ({play['group']}): {play['title']}"
|
|
139
|
+
return _prompt
|
|
140
|
+
|
|
141
|
+
for play in DEFAULT_PLAYS:
|
|
142
|
+
mcp.prompt()(_make(play))
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""MCP Resources — the guidance layer (mental model + how-to-combine).
|
|
2
|
+
|
|
3
|
+
These teach the assistant HOW GEN works and how to combine tools into outcomes
|
|
4
|
+
(the knowledge a flat tool list lacks). The mental model spans the FULL breadth
|
|
5
|
+
of GEN; recipes show how to combine capabilities; the credits/errors note covers
|
|
6
|
+
failure handling. The daily guidance Task can regenerate richer versions later.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
MENTAL_MODEL = """\
|
|
14
|
+
# GEN — the full mental model
|
|
15
|
+
|
|
16
|
+
GEN is an end-to-end platform for autonomous social video. It turns a brand or
|
|
17
|
+
idea into finished, on-brand short-form (and stitched long-form) video, and runs
|
|
18
|
+
the whole loop — research → ideate → produce → publish → monitor — on autopilot.
|
|
19
|
+
|
|
20
|
+
## The core objects
|
|
21
|
+
- **Workspace / Organization** — billing container; holds agents and credits.
|
|
22
|
+
- **Agent (an "AI human")** — a persistent identity: brand, persona, voice, look,
|
|
23
|
+
avatars, linked social accounts, monitored topics. Everything is scoped to an
|
|
24
|
+
agent_id.
|
|
25
|
+
- **Vidsheet (Auto Content Engine)** — a spreadsheet-as-video-editor: rows are
|
|
26
|
+
scenes/variants, columns are ingredients, cells hold content, and layers stack
|
|
27
|
+
(video / image / audio / captions / text overlays) into a final render.
|
|
28
|
+
- **Generation** — any AI job: text, image, video, speech, lipsync, captions.
|
|
29
|
+
- **Content idea / Play / Task** — the planning + automation layer.
|
|
30
|
+
|
|
31
|
+
## What GEN can do (breadth)
|
|
32
|
+
|
|
33
|
+
**1. Research & trends**
|
|
34
|
+
- Trending videos / hashtags / sounds by niche, region, timeframe.
|
|
35
|
+
- Creator discovery; analyze a creator's or your own hooks & formats.
|
|
36
|
+
- Cross-platform web research (Reddit, X, YouTube, TikTok, IG, HN, etc.).
|
|
37
|
+
- Comment mining: audience sentiment on a topic or competitors.
|
|
38
|
+
- Watchlists: saved topics/accounts monitored over time + weekly reports.
|
|
39
|
+
|
|
40
|
+
**2. Ideation**
|
|
41
|
+
- Data-driven content ideas tuned to what's working now in your niche.
|
|
42
|
+
- Ideas adapted from a specific reference video (analyze → adapt to brand).
|
|
43
|
+
- Refine ideas conversationally; set persistent creative preferences.
|
|
44
|
+
|
|
45
|
+
**3. Production (the vidsheet engine)**
|
|
46
|
+
- Clone a template (fastest path) or build a vidsheet from scratch.
|
|
47
|
+
- Generate images — Gemini "Nano Banana" family incl. **nano-banana-pro**
|
|
48
|
+
(Gemini 3 Pro Image), 2K, aspect 1:1/9:16/16:9.
|
|
49
|
+
- Generate video — Seedance (1.0/1.5/2.0), Veo, Kling, Sora, Pika; short clips
|
|
50
|
+
(Seedance 2.0 up to ~15s) with ratios incl. 21:9.
|
|
51
|
+
- Voice — list/create/design/clone voices; text-to-speech.
|
|
52
|
+
- Avatars & talking avatars — create, train custom avatars, talking-head video,
|
|
53
|
+
lipsync.
|
|
54
|
+
- Captions, text overlays, music selection, B-roll import (any length, from a
|
|
55
|
+
URL), background removal, upscaling, image variants.
|
|
56
|
+
- **Render** — composite all layers/rows into ONE deliverable. This is how you
|
|
57
|
+
assemble multi-scene and long-form video (combine many clips + B-roll → render).
|
|
58
|
+
|
|
59
|
+
**4. Publishing & scheduling**
|
|
60
|
+
- Publish or schedule posts to **tiktok / instagram / youtube / x**.
|
|
61
|
+
- schedule_type: now | specific_time (calendar) | next slot. Full post calendar
|
|
62
|
+
CRUD (add / list / update / delete).
|
|
63
|
+
- Connect social accounts via an OAuth URL the user opens (no credentials handled
|
|
64
|
+
by the MCP).
|
|
65
|
+
|
|
66
|
+
**5. Automation (Tasks)**
|
|
67
|
+
- Recurring jobs: run any prompt on a schedule (daily/weekly/monthly). A fired
|
|
68
|
+
Task runs the SAME composer as chat, so a daily Task can generate + render +
|
|
69
|
+
(with confirmation) auto-publish video. Pause/resume/run-now/delete.
|
|
70
|
+
|
|
71
|
+
**6. Billing / self-service**
|
|
72
|
+
- Read credit balance + usage history (analyze your spend).
|
|
73
|
+
- Buy credits / upgrade workspace (confirm price with the user first — real money).
|
|
74
|
+
|
|
75
|
+
## Two ways to drive GEN via this MCP
|
|
76
|
+
- **Deterministic tools** (gen_create_row, gen_create_video, gen_render_video,
|
|
77
|
+
gen_schedule_post, ...) — for EXACT single actions you know you want.
|
|
78
|
+
- **gen_do(goal)** — delegate a natural-language OUTCOME to the GEN composer,
|
|
79
|
+
which classifies intent and orchestrates multi-step (e.g. "make a 5-scene 16:9
|
|
80
|
+
video about X and schedule it for tomorrow 9am"). Use this for composed goals.
|
|
81
|
+
|
|
82
|
+
## Knowing what's possible vs how to combine
|
|
83
|
+
Most real outcomes are COMBINATIONS (research → idea → produce → publish → repeat
|
|
84
|
+
daily). A single clip is short; long/multi-scene video comes from combining clips
|
|
85
|
+
+ B-roll then rendering. When in doubt, describe the OUTCOME to gen_do and let the
|
|
86
|
+
composer sequence the steps.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
LONGFORM_RECIPE = """\
|
|
90
|
+
# Recipe: build a long / multi-scene video
|
|
91
|
+
|
|
92
|
+
A single generation is one short clip (~5-15s). To make a long or multi-scene
|
|
93
|
+
video, COMBINE clips + imported B-roll, then render:
|
|
94
|
+
|
|
95
|
+
1. gen_create_engine (or gen_clone_template) → a vidsheet.
|
|
96
|
+
2. For each scene: gen_create_row, then gen_create_video (per-scene prompt).
|
|
97
|
+
For images use gen_create_image with version='nano-banana-pro' (Gemini 3 Pro).
|
|
98
|
+
3. Import long B-roll with gen_import_asset_from_url (YouTube/TikTok/direct file —
|
|
99
|
+
no length limit) and place as layers.
|
|
100
|
+
4. gen_render_video on the final_video cell → ONE combined video.
|
|
101
|
+
|
|
102
|
+
Or just: gen_do("create a 5-scene 16:9 video about <topic>") and let the
|
|
103
|
+
composer do all of the above.
|
|
104
|
+
|
|
105
|
+
To do it every day: gen_create_recurring_job(prompt="create a video about ...",
|
|
106
|
+
cadence="daily", auto_publish=true) — confirm the details first.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
CREDITS_NOTE = """\
|
|
110
|
+
# Credits & errors
|
|
111
|
+
|
|
112
|
+
Paid operations (generation, voice, research) cost credits. If a tool returns
|
|
113
|
+
insufficient_credits=true, the user is OUT OF CREDITS — tell them plainly and
|
|
114
|
+
offer gen_buy_credits (confirm the plan/price first) or gen_upgrade_workspace.
|
|
115
|
+
Check gen_get_credit_balance before large jobs; estimate with gen_estimate_job;
|
|
116
|
+
analyze spend with gen_get_credit_usage.
|
|
117
|
+
|
|
118
|
+
All backend errors are surfaced as {ok:false, error, error_code} — present the
|
|
119
|
+
message to the user; never swallow it. Purchases and publishing are real-world
|
|
120
|
+
actions — confirm with the user before executing.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def register(mcp: Any) -> None:
|
|
125
|
+
@mcp.resource("gen://mental-model")
|
|
126
|
+
def mental_model() -> str:
|
|
127
|
+
return MENTAL_MODEL
|
|
128
|
+
|
|
129
|
+
@mcp.resource("gen://recipes/long-form-video")
|
|
130
|
+
def longform() -> str:
|
|
131
|
+
return LONGFORM_RECIPE
|
|
132
|
+
|
|
133
|
+
@mcp.resource("gen://credits-and-errors")
|
|
134
|
+
def credits() -> str:
|
|
135
|
+
return CREDITS_NOTE
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""GEN MCP server entry point (FastMCP).
|
|
2
|
+
|
|
3
|
+
Exposes the GEN platform to MCP clients. Tools mirror what a user can do through
|
|
4
|
+
the GEN front end; gen_do delegates natural-language goals to the GEN composer.
|
|
5
|
+
Backend errors — especially out-of-credits — are surfaced to the user cleanly.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from fastmcp import FastMCP
|
|
11
|
+
|
|
12
|
+
from . import prompts, resources, tools
|
|
13
|
+
|
|
14
|
+
mcp = FastMCP("gen")
|
|
15
|
+
|
|
16
|
+
tools.register_all(mcp)
|
|
17
|
+
resources.register_all(mcp)
|
|
18
|
+
prompts.register_all(mcp)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
"""Run over stdio (the transport MCP clients use)."""
|
|
23
|
+
mcp.run()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
main()
|