This is page 1 of 4. Use http://codebase.md/feed-mob/fm-mcp-servers?page={x} to view the full context.
# Directory Structure
```
├── .claude
│ └── settings.local.json
├── .cursor
│ └── rules
│ └── typescript-indentation.mdc
├── .github
│ └── workflows
│ ├── publish-civitai-records.yml
│ ├── publish-github-issues.yml
│ ├── publish-imagekit.yml
│ ├── publish-n8n-nodes-feedmob-direct-spend-visualizer.yml
│ ├── publish-reporting-packages.yml
│ └── publish-work-journals.yml
├── .gitignore
├── AGENTS.md
├── package-lock.json
├── README.md
├── src
│ ├── applovin-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── appsamurai-reporting
│ │ ├── .env.example
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── civitai-records
│ │ ├── .gitignore
│ │ ├── build.sh
│ │ ├── docker-compose.yml
│ │ ├── docs
│ │ │ └── civitai-owner-createrole.md
│ │ ├── env.sample
│ │ ├── infra
│ │ │ └── db-init
│ │ │ ├── 01-roles.sql
│ │ │ ├── 02_functions.sql
│ │ │ ├── 03_init.sql
│ │ │ ├── 04_create_prompts.sql
│ │ │ ├── 05_create_assets.sql
│ │ │ ├── 06_create_civitai_posts.sql
│ │ │ ├── 07_add_constraints_and_input_assets.sql
│ │ │ ├── 08_migration_add_on_behalf_of.sql
│ │ │ ├── 09_migration_update_functions_for_on_behalf_of.sql
│ │ │ ├── 10_update_on_behalf_of_for_existing_records.sql
│ │ │ ├── 11_create_asset_stats.sql
│ │ │ ├── 12_fix_trigger_for_tables_without_on_behalf_of.sql
│ │ │ └── 13_add_columns_to_asset_stats.sql
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── prisma
│ │ │ └── schema.prisma
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── lib
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── detectRemoteAssetType.test.ts
│ │ │ │ │ └── sha256.test.ts
│ │ │ │ ├── civitaiApi.ts
│ │ │ │ ├── detectRemoteAssetType.ts
│ │ │ │ ├── handleDatabaseError.ts
│ │ │ │ ├── prisma.ts
│ │ │ │ └── sha256.ts
│ │ │ ├── prompts
│ │ │ │ ├── civitai-media-engagement.md
│ │ │ │ ├── civitaiMediaEngagement.ts
│ │ │ │ ├── record-civitai-workflow.md
│ │ │ │ └── recordCivitaiWorkflow.ts
│ │ │ ├── server.ts
│ │ │ └── tools
│ │ │ ├── calculateSha256.ts
│ │ │ ├── createAsset.ts
│ │ │ ├── createCivitaiPost.ts
│ │ │ ├── createPrompt.ts
│ │ │ ├── fetchCivitaiPostAssets.ts
│ │ │ ├── findAsset.ts
│ │ │ ├── getMediaEngagementGuide.ts
│ │ │ ├── getWorkflowGuide.ts
│ │ │ ├── listCivitaiPosts.ts
│ │ │ ├── syncPostAssetStats.ts
│ │ │ └── updateAsset.ts
│ │ └── tsconfig.json
│ ├── feedmob-reporting
│ │ ├── .env.example
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── api.ts
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── femini-reporting
│ │ ├── Dockerfile
│ │ ├── femini_mcp_guide.md
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── github-issues
│ │ ├── common
│ │ │ ├── errors.ts
│ │ │ ├── types.ts
│ │ │ ├── utils.ts
│ │ │ └── version.ts
│ │ ├── index.ts
│ │ ├── operations
│ │ │ ├── issues.ts
│ │ │ └── search.ts
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ └── tsconfig.json
│ ├── imagekit
│ │ ├── env.sample
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── server.ts
│ │ │ ├── services
│ │ │ │ ├── imageKitUpload.ts
│ │ │ │ └── imageUploader.ts
│ │ │ └── tools
│ │ │ ├── cropAndWatermark.ts
│ │ │ └── uploadFile.ts
│ │ └── tsconfig.json
│ ├── impact-radius-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── fm_impact_radius_mapping.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ └── yarn.lock
│ ├── inmobi-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── ironsource-aura-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── ironsource-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── jampp-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── kayzen-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── index.ts
│ │ │ └── kayzen-client.ts
│ │ └── tsconfig.json
│ ├── liftoff-reporting
│ │ ├── .env.example
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── mintegral-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── n8n-nodes-feedmob-direct-spend-visualizer
│ │ ├── AGENTS.md
│ │ ├── credentials
│ │ │ └── FeedmobDirectSpendVisualizerApi.credentials.ts
│ │ ├── index.ts
│ │ ├── nodes
│ │ │ └── FeedmobDirectSpendVisualizer
│ │ │ ├── FeedmobDirectSpendVisualizer.node.ts
│ │ │ └── logo.svg
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── scripts
│ │ │ ├── build-assets.js
│ │ │ └── setup-plugin.js
│ │ ├── tsconfig.json
│ │ └── types.d.ts
│ ├── n8n-nodes-sensor-tower
│ │ ├── credentials
│ │ │ └── SensorTowerApi.credentials.ts
│ │ ├── index.ts
│ │ ├── MCP_to_n8n_Guide_zh.md
│ │ ├── nodes
│ │ │ └── SensorTower
│ │ │ ├── logo.svg
│ │ │ └── SensorTower.node.ts
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── tsconfig.json
│ │ └── types.d.ts
│ ├── rtb-house-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── samsung-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── sensor-tower-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── singular-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── smadex-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── tapjoy-reporting
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── user-activity-reporting
│ │ ├── .env.example
│ │ ├── package-lock.json
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── api.ts
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ └── work-journals
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/src/appsamurai-reporting/.env.example:
--------------------------------------------------------------------------------
```
APPSAMURAI_API_KEY="YOUR_API_KEY_HERE"
```
--------------------------------------------------------------------------------
/src/liftoff-reporting/.env.example:
--------------------------------------------------------------------------------
```
APPSAMURAI_API_KEY="YOUR_API_KEY_HERE"
```
--------------------------------------------------------------------------------
/src/civitai-records/.gitignore:
--------------------------------------------------------------------------------
```
node_modules
.npm-cache
# Keep environment variables out of version control
.env
```
--------------------------------------------------------------------------------
/src/feedmob-reporting/.env.example:
--------------------------------------------------------------------------------
```
FEEDMOB_KEY=your_feedmob_key_here
FEEDMOB_SECRET=your_feedmob_secret_here
FEEDMOB_API_BASE=your_feedmob_api_url
```
--------------------------------------------------------------------------------
/src/user-activity-reporting/.env.example:
--------------------------------------------------------------------------------
```
# Feedmob Admin API
FEEDMOB_API_BASE=https://admin.feedmob.com
FEEDMOB_KEY=your_feedmob_key
FEEDMOB_SECRET=your_feedmob_secret
# Slack API (for future use)
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
# HubSpot API (for future use)
HUBSPOT_ACCESS_TOKEN=your_hubspot_access_token
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
build/
gcp-oauth.keys.json
.*-server-credentials.json
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
.DS_Store
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.claude/settings.local.json
```
--------------------------------------------------------------------------------
/src/feedmob-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Feedmob Spend MCP Server
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json
3. Add the Feedmob Spend MCP server to the configuration:
### NPX
```json
{
"mcpServers": {
"feedmob": {
"command": "npx",
"args": [ "-y", "@feedmob/feedmob-reporting" ],
"env": {
"FEEDMOB_KEY": "FEEDMOB_KEY",
"FEEDMOB_SECRET": "FEEDMOB_SECRET",
"FEEDMOB_API_BASE": "FEEDMOB_API_BASE"
}
}
}
}
```
## License
MIT
```
--------------------------------------------------------------------------------
/src/jampp-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Jampp Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for [Jampp Reporting API](https://developers.jampp.com/docs/reporting-api).
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json
3. Add the Jampp MCP server to the configuration:
### NPX
```json
{
"mcpServers": {
"jampp": {
"command": "npx",
"args": [ "-y", "@feedmob/jampp-reporting" ],
"env": {
"JAMPP_CLIENT_ID": "your_client_id",
"JAMPP_CLIENT_SECRET": "your_client_secret"
}
}
}
}
```
## License
MIT
```
--------------------------------------------------------------------------------
/src/singular-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Singular Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for [Singular Reporting](https://support.singular.net/hc/en-us/articles/360045245692-Reporting-API-Reference).
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json
3. Add the Jampp MCP server to the configuration:
### NPX
```json
{
"mcpServers": {
"singular": {
"command": "npx",
"args": [ "-y", "@feedmob/singular-reporting" ],
"env": {
"SINGULAR_API_KEY": "api_key",
"SINGULAR_API_BASE_URL": "api_base_url"
}
}
}
}
```
## License
MIT
```
--------------------------------------------------------------------------------
/src/kayzen-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Kayzen Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for [Kayzen Reporting API](https://developers.kayzen.io/reference/list-reports).
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json
3. Add the Jampp MCP server to the configuration:
### NPX
```json
{
"mcpServers": {
"kayzen": {
"command": "npx",
"args": [ "-y", "@feedmob/kayzen-reporting" ],
"env": {
"KAYZEN_USERNAME": "user_email",
"KAYZEN_PASSWORD": "user_password",
"KAYZEN_BASIC_AUTH": "baisc_auth_token"
}
}
}
}
```
## License
MIT
```
--------------------------------------------------------------------------------
/src/femini-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Feedmob Femini MCP Server
## Features
- 🔍 **Flexible Data Querying**: Supports various grouping methods and filtering conditions
- 📊 **Rich Metrics**: Includes key metrics such as spend, revenue, conversion rate, etc.
- 📚 **Detailed Documentation**: Built-in user manual and query templates
- 🚀 **Easy Integration**: Based on the FastMCP framework, supports multiple transport methods
## Installation and Setup
Visit femini dash specified page to get mcp server json configuration
## Tools
### 1. query_campaign_spends
The primary tool for querying ad campaign data.
**Parameters:**
- `guide`: Required, user manual URI (`campaign-spends-api-guide://usage`)
- `date_gteq`: Start date (YYYY-MM-DD)
- `date_lteq`: End date (YYYY-MM-DD)
- `groups`: Array of grouping methods (day/week/month/client/partner/campaign/click_url/country)
- `metrics`: Array of metrics (gross/net/revenue/impressions/clicks/installs/cvr/margin)
- `legacy_client_id_in`: Client ID filter
- `legacy_partner_id_in`: Partner ID filter
- `legacy_campaign_id_in`: Campaign ID filter
- `legacy_click_url_id_in`: Click URL ID filter
### 2. search_ids
Retrieves client, partner, and campaign ID information based on keywords.
**Parameters:**
- `keys`: List of keywords (array of strings)
## Resources
### 1. CampaignSpendsApiQuery User Manual
URI: `campaign-spends-api-guide://usage`
Detailed API parameter descriptions and usage guide.
## Development and Debugging
### Local Development
```bash
# Start development server
npm run dev
# Test with MCP CLI
npx fastmcp dev src/index.ts
# Debug with MCP Inspector
npx fastmcp inspect src/index.ts
```
### Type Checking
```bash
npx tsc --noEmit
```
## License
MIT License
## Contributions
Welcome to submit Issues and Pull Requests to improve this project.
# feedmob_femini_mcp_server
```
--------------------------------------------------------------------------------
/src/work-journals/README.md:
--------------------------------------------------------------------------------
```markdown
# Feedmob Work Journals MCP Server
## Features
* 📝 **Work Journal Management**: Supports querying, creating, and updating work journals.
* 🔍 **Flexible Querying**: Supports filtering logs by date range, user ID, and team ID.
* 🔗 **API Integration**: Interacts with backend APIs by configuring `API_URL` and `API_TOKEN`.
* 📚 **Built-in Resources**: Provides Schema information for the Time Off API.
## Installation and Setup
Visit feedmob time off dash specified page to get mcp server json configuration
## Tools
### 1. `query_journals`
Queries work journals, supporting filtering by date range, user ID, and team ID.
**Parameters:**
* `scheam`: Required, get from system resources `time-off-api-scheam://usage`.
* `start_date`: Start date (YYYY-MM-DD format), defaults to 7 days before today.
* `end_date`: End date (YYYY-MM-DD format), defaults to today.
* `current_user_only`: Optional, whether to query only the current user's work journals (true/false).
* `user_ids`: Optional, list of user IDs (array).
* `team_ids`: Optional, list of team IDs (array).
### 2. `create_or_update_journal`
Creates or updates a work journal.
**Parameters:**
* `date`: Required, journal date (YYYY-MM-DD format).
* `content`: Required, journal content.
## Resources
### 1. Time Off API Schema, including Teams, Users Information
URI: `time-off-api-scheam://usage`
Provides Schema information for the Time Off API, including team and user information.
## Development and Debugging
### Local Development
```bash
# Start development server
npm run dev
# Test with MCP CLI
npx fastmcp dev src/index.ts
# Debug with MCP Inspector
npx fastmcp inspect src/index.ts
```
### Type Checking
```bash
npx tsc --noEmit
```
## License
MIT License
## Contributions
Welcome to submit Issues and and Pull Requests to improve this project.
```
--------------------------------------------------------------------------------
/src/appsamurai-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# AppSamurai Reporting MCP Server
This MCP server provides tools and prompts to interact with the [AppSamurai Campaign Spend API](https://help.appsamurai.com/en/articles/11105087-appsamurai-campaign-spend-api).
## Features
- **Tools**:
- `get_appsamurai_campaign_spend`: Fetches campaign spending data for a specified date range with optional filtering.
- **Prompts**:
- `check_appsamurai_campaign_spend`: Guides users on how to check campaign spend.
## Parameters
The following parameters are supported:
- **Required**:
- `startDate`: Start date in YYYY-MM-DD format
- `endDate`: End date in YYYY-MM-DD format
- **Optional Filters**:
- `campaignId`: Filter by specific campaign ID
- `bundleId`: Filter by specific application bundle ID
- `platform`: Filter by platform (e.g., ios, play)
- `campaignName`: Filter by campaign name
- `country`: Filter by country in ISO 3166-1 alpha-2 format (e.g., US, GB)
## Setup
1. **Install dependencies**:
```bash
npm install
```
2. **Configure environment variables**:
- Copy `.env.example` to `.env`.
- Fill in your `APPSAMURAI_API_KEY` in the `.env` file.
3. **Build the server**:
```bash
npm run build
```
## Running the Server
To run the server directly for testing:
```bash
npm start
```
## Connecting to a Client (e.g., Claude Desktop)
Add the following configuration to your client's MCP server settings (e.g., `claude_desktop_config.json`), replacing `/path/to/appsamurai-reporting` with the actual absolute path:
```json
{
"mcpServers": {
"appsamurai-reporting": {
"command": "npx",
"args": [ "-y", "@feedmob/appsamurai-reporting"],
"env": {
"APPSAMURAI_API_KEY": "api_key"
}
}
}
}
```
Remember to set the `APPSAMURAI_API_KEY` in the environment where the client (like Claude Desktop) runs or add it to the `env` block in the client configuration if supported.
```
--------------------------------------------------------------------------------
/src/liftoff-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Liftoff Reporting MCP Server
This MCP server provides tools to interact with the [Liftoff Reporting API](https://docs.liftoff.io/advertiser/reporting_api).
## Features
- **Tools**:
- `create_liftoff_report`: Generates a new report based on specified parameters.
- `check_liftoff_report_status`: Checks the status of a previously generated report.
- `download_liftoff_report_data`: Downloads the data for a completed report (CSV or JSON).
- `list_liftoff_apps`: Fetches details of available Liftoff applications.
- `list_liftoff_campaigns`: Fetches details of available Liftoff campaigns.
- `download_liftoff_report_with_names`: Downloads report data and enriches it with campaign names.
- **Prompts**: (Currently no specific prompts defined)
## Setup
1. **Install dependencies**:
```bash
npm install
```
2. **Configure environment variables**:
- Copy `.env.example` to `.env` (if it exists, otherwise create `.env`).
- Fill in your `LIFTOFF_API_KEY` and `LIFTOFF_API_SECRET` in the `.env` file.
3. **Build the server**:
```bash
npm run build
```
## Running the Server
To run the server directly for testing:
```bash
npm start
```
## Connecting to a Client (e.g., Cursor)
Add the following configuration to your client's MCP server settings (e.g., in Cursor settings), replacing `/path/to/liftoff-reporting` with the actual absolute path if running locally, or using the package name if installed globally:
```json
{
"mcpServers": {
"liftoff-reporting": {
"command": "npx",
"args": ["-y", "@feedmob/liftoff-reporting"], // Replace with correct package name if needed
"env": {
"LIFTOFF_API_KEY": "your_liftoff_api_key",
"LIFTOFF_API_SECRET": "your_liftoff_api_secret"
}
}
}
}
```
Remember to set the `LIFTOFF_API_KEY` and `LIFTOFF_API_SECRET` in the environment where the client runs or add them to the `env` block in the client configuration.
```
--------------------------------------------------------------------------------
/src/user-activity-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# User Activity Reporting MCP
MCP server for querying client contacts, Slack messages, and HubSpot tickets.
## Features
| Tool | Description |
|------|-------------|
| `get_all_client_contacts` | List all clients with team members |
| `get_client_team_members` | Get team (AA, AM, AE, PM, PA, AO) for a client |
| `get_clients_by_pod` | List clients in a POD team |
| `get_clients_by_name` | Find clients by person name |
| `get_user_slack_history` | Search Slack messages from a user |
| `get_hubspot_tickets` | Query HubSpot tickets |
| `get_hubspot_ticket_detail` | Get ticket details |
| `get_hubspot_tickets_by_user` | Find tickets by owner |
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `FEEDMOB_API_BASE` | Yes | Feedmob Admin API URL (e.g., `https://admin.feedmob.com`) |
| `FEEDMOB_KEY` | Yes | Feedmob API key |
| `FEEDMOB_SECRET` | Yes | Feedmob API secret |
| `SLACK_BOT_TOKEN` | No | Slack Bot token for message search |
| `HUBSPOT_ACCESS_TOKEN` | No | HubSpot private app token |
## Setup
```bash
cd src/user-activity-reporting
npm install
npm run build
```
## Development
```bash
npm run dev # Run with hot reload
npm run inspect # Test tools interactively
npm run build # Compile to dist/
```
## MCP Configuration
```json
{
"mcpServers": {
"user-activity-reporting": {
"command": "npx",
"args": ["-y", "@feedmob/user-activity-reporting"],
"env": {
"FEEDMOB_API_BASE": "https://admin.feedmob.com",
"FEEDMOB_KEY": "your_key",
"FEEDMOB_SECRET": "your_secret",
"SLACK_BOT_TOKEN": "xoxb-xxx",
"HUBSPOT_ACCESS_TOKEN": "pat-xxx"
}
}
}
}
```
## Usage Examples
```
# Get team for a client
Tool: get_client_team_members
Args: { "client_name": "Uber" }
# Find clients by person
Tool: get_clients_by_name
Args: { "name": "John", "role": "am" }
# Search Slack messages
Tool: get_user_slack_history
Args: { "user_name": "John", "query": "budget" }
```
```
--------------------------------------------------------------------------------
/src/tapjoy-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# TapJoy Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for [TapJoy Reporting API](https://api.tapjoy.com/graphql/docs/guide-getting_started).
## Features
This server provides the following tool:
* **`get_advertiser_adset_spend`**: Retrieves spend data for active advertiser ad sets within a specified date range.
* **Input Parameters**:
* `start_date` (string): The start date for the report in `YYYY-MM-DD` format.
* `end_date` (string): The end date for the report in `YYYY-MM-DD` format.
* **Output**: Returns a JSON array of objects, each containing the campaign name (`campaign.name`) and the spend in USD (`insights.reports[0].spendUSD`).
## Setup
1. **Environment Variables**: Before running the server, you need to set the `TAPJOY_API_KEY` environment variable. This key is used for authenticating with the Tapjoy API. You can obtain this key from your Tapjoy account.
```bash
export TAPJOY_API_KEY='your_tapjoy_api_key'
```
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the Tapjoy MCP server to the `mcpServers` configuration section:
```json
{
"mcpServers": {
"tapjoy": {
"command": "npx",
"args": [ "-y", "@feedmob/tapjoy-reporting" ]
"env": {
"TAPJOY_API_KEY": "your_tapjoy_api_key_base64_encoded"
}
} // ... other servers
}
}
```
*Replace `/path/to/fm-mcp-servers/src/tapjoy-reporting/dist/index.js` with the actual path to the compiled JavaScript file after building the project.*
*Alternatively, if published to npm, you could use `npx` similar to the previous Jampp example.*
## Development
1. Clone the repository.
2. Navigate to the `src/tapjoy-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variable (`TAPJOY_API_KEY`).
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/src/smadex-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Smadex Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for Smadex Reporting API.
## Features
This server provides the following tools:
* **`get_smadex_report_id`**: Creates a Smadex report request and returns the report ID.
* **Input Parameters**:
* `startDate` (string, required): Start date for the report in `YYYY-MM-DD` format.
* `endDate` (string, required): End date for the report in `YYYY-MM-DD` format.
* **Output**: Returns the report ID as text.
* **`get_smadex_report_download_url`**: Gets the download URL for a Smadex report by its ID.
* **Input Parameters**:
* `reportId` (string, required): The report ID returned from get_smadex_report_id.
* **Output**: Returns the download URL as text.
* **`get_smadex_report`**: Downloads and returns report data from a Smadex report download URL.
* **Input Parameters**:
* `downloadUrl` (string, required): The download URL for the report.
* **Output**: Returns the report data as text.
## Setup
1. **Environment Variables**: Before running the server, you need to set the following environment variables:
```bash
export SMADEX_API_BASE_URL='your_smadex_api_base_url'
export SMADEX_EMAIL='your_smadex_email'
export SMADEX_PASSWORD='your_smadex_password'
```
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the Smadex MCP server to the `mcpServers` configuration section:
```json
{
"mcpServers": {
"smadex": {
"command": "npx",
"args": [ "-y", "@feedmob/smadex-reporting" ]
"env": {
"SMADEX_API_BASE_URL": "your_smadex_api_base_url",
"SMADEX_EMAIL": "your_smadex_email",
"SMADEX_PASSWORD": "your_smadex_password"
}
}
}
}
```
## Development
1. Clone the repository.
2. Navigate to the `src/smadex-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variables (`SMADEX_API_BASE_URL`, `SMADEX_EMAIL`, and `SMADEX_PASSWORD`).
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/src/impact-radius-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Impact Radius Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for [Impact Radius](https://impact.com/) affiliate marketing reporting with FeedMob campaign mapping integration.
## Features
- **Campaign Mapping Integration**: Automatically fetches campaign mappings from FeedMob API to enrich Impact Radius data
- **Action List Reporting**: Retrieves action lists from Impact Radius API for specified date ranges
- **Data Enrichment**: Combines Impact Radius action data with campaign context including client names and campaign information
- **Flexible Filtering**: Supports filtering by campaign, ad, and event type through mapping configurations
## Available Tools
### fetch_action_list_from_impact_radius
Fetches action list from Impact Radius API with campaign mapping integration for a date range.
**Parameters:**
- `start_date` (string, required): Start date in YYYY-MM-DD format
- `end_date` (string, required): End date in YYYY-MM-DD format
**Returns:**
- JSON object with `allrecords` array containing enriched action data
- Each record includes original Impact Radius data plus mapping fields:
- `mapping_impact_brand`: Impact brand from mapping
- `mapping_impact_ad`: Impact ad from mapping
- `mapping_impact_event_type`: Impact event type from mapping
- `campaign`: Campaign name from FeedMob
- `client_name`: Client name from FeedMob
- `total_count`: Total number of records returned
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
- macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
- Windows: %APPDATA%\Claude\claude_desktop_config.json
3. Add the Impact Radius MCP server to the configuration:
### NPX
```json
{
"mcpServers": {
"impact-radius": {
"command": "npx",
"args": [ "-y", "@feedmob/impact-radius-reporting" ],
"env": {
"IMPACT_RADIUS_SID": "your_impact_radius_sid",
"IMPACT_RADIUS_TOKEN": "your_impact_radius_token",
"FEEDMOB_KEY": "your_feedmob_key",
"FEEDMOB_SECRET": "your_feedmob_secret",
"FEEDMOB_API_BASE": "your_api_base_url"
}
}
}
}
```
## Authentication Requirements
This server requires authentication for both Impact Radius and FeedMob APIs:
- **Impact Radius**: Requires `IMPACT_RADIUS_SID` and `IMPACT_RADIUS_TOKEN` for HTTP Basic Authentication
- **FeedMob**: Requires `FEEDMOB_KEY` and `FEEDMOB_SECRET` for JWT authentication to access campaign mappings
## License
MIT
```
--------------------------------------------------------------------------------
/src/applovin-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# AppLovin Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for [AppLovin Reporting API](https://dash.applovin.com/documentation/mediation/reporting-api).
## Features
This server provides the following tool:
* **`get_advertiser_report`**: Retrieves campaign spending data from AppLovin Reporting API for advertisers.
* **Input Parameters**:
* `start_date` (string, required): The start date for the report in `YYYY-MM-DD` format.
* `end_date` (string, required): The end date for the report in `YYYY-MM-DD` format.
* `columns` (string, optional): Comma-separated list of columns to include (e.g., 'day,campaign,impressions,clicks,conversions,cost').
* `format` (string, optional): Format of the report data ('json' or 'csv'). Default is 'json'.
* `filter_campaign` (string, optional): Filter results by campaign name.
* `filter_country` (string, optional): Filter results by country (e.g., 'US,JP').
* `filter_platform` (string, optional): Filter results by platform (e.g., 'android,ios').
* `sort_column` (string, optional): Column to sort by (e.g., 'cost').
* `sort_order` (string, optional): Sort order ('ASC' or 'DESC').
* **Output**: Returns the report data in the specified format (JSON or CSV).
## Setup
1. **Environment Variables**: Before running the server, you need to set the `APPLOVIN_API_KEY` environment variable. This key is used for authenticating with the AppLovin API. You can obtain this key from your AppLovin account.
```bash
export APPLOVIN_API_KEY='your_applovin_api_key'
```
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the AppLovin MCP server to the `mcpServers` configuration section:
```json
{
"mcpServers": {
"applovin": {
"command": "npx",
"args": [ "-y", "@feedmob/applovin-reporting" ]
"env": {
"APPLOVIN_API_KEY": "your_applovin_api_key"
}
}
}
}
```
## Development
1. Clone the repository.
2. Navigate to the `src/applovin-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variable (`APPLOVIN_API_KEY`).
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/src/rtb-house-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# RTB House Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for RTB House API.
## Features
This server provides the following tools:
* **`get_rtb_house_data`**: Fetch reporting data from RTB House API for a specific date range.
* **Input Parameters**:
* `dateFrom` (string, required): Start date for the report in `YYYY-MM-DD` format.
* `dateTo` (string, required): End date for the report in `YYYY-MM-DD` format.
* `app` (string, optional): Optional app/advertiser name to filter results. If not provided, returns data for all advertisers.
* `maxRetries` (integer, optional): Maximum number of retry attempts (default: 3).
* **Output**: Returns a mapping of app/advertiser name to data array as JSON. Each data item includes fields such as `day`, `subcampaign`, `impsCount`, `clicksCount`, `campaignCost`, etc.
## Setup
1. **Environment Variables**: Before running the server, you need to set the following environment variables:
```bash
export RTB_HOUSE_USER='your_rtbhouse_username'
export RTB_HOUSE_PASSWORD='your_rtbhouse_password'
```
**Required Environment Variables:**
* `RTB_HOUSE_USER`: RTB House API username
* `RTB_HOUSE_PASSWORD`: RTB House API password
## Usage
1. Start the server after setting the required environment variables:
```bash
node dist/index.js
```
2. The server exposes the following tool:
* **get_rtb_house_data**
* `dateFrom` (string, required): Start date in `YYYY-MM-DD` format
* `dateTo` (string, required): End date in `YYYY-MM-DD` format
* `app` (string, optional): App/advertiser name (case-insensitive)
* `maxRetries` (integer, optional): Maximum retry attempts (default: 3)
* **Returns**: `{ [appName]: [ { day, subcampaign, impsCount, clicksCount, campaignCost, ... } ] }`
Example request:
```json
{
"dateFrom": "2025-06-10",
"dateTo": "2025-06-15"
}
```
Example response:
```json
{
"US_ZipRecruiter_App": [
{
"subcampaign": "campaign_name",
"day": "2025-06-14",
"impsCount": 138733,
"clicksCount": 4946,
"campaignCost": 378.49
},
...
]
}
```
## Development
1. Clone the repository.
2. Navigate to the `src/rtb-house-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variables (`RTB_HOUSE_USER`, `RTB_HOUSE_PASSWORD`).
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/src/mintegral-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Mintegral Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for Mintegral Reporting API.
## Features
This server provides the following tool:
* **`get_mintegral_performance_report`**: Retrieves performance data from Mintegral Reporting API.
* **Input Parameters**:
* `start_date` (string, required): Start date for the report in `YYYY-MM-DD` format.
* `end_date` (string, required): End date for the report in `YYYY-MM-DD` format.
* `utc` (string, optional): Timezone (default: '+8').
* `per_page` (number, optional): Number of results per page (max: 5000). Default is 50.
* `page` (number, optional): Page number. Default is 1.
* `dimension` (string, optional): Data dimension (e.g., 'location', 'sub_id', 'creative').
* `uuid` (string, optional): Filter by uuid.
* `campaign_id` (number, optional): Filter by campaign_id.
* `package_name` (string, optional): Filter by android bundle id or ios app store id.
* `not_empty_field` (string, optional): Fields that can't be empty (comma-separated: 'click', 'install', 'impression', 'spend').
* **Output**: Returns the report data in JSON format.
## API Limitations
* Date range cannot exceed 8 days for a single request
* The per_page parameter cannot exceed 5000
## Setup
1. **Environment Variables**: Before running the server, you need to set the following environment variables:
```bash
export MINTEGRAL_ACCESS_KEY='your_mintegral_access_key'
export MINTEGRAL_API_KEY='your_mintegral_api_key'
```
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the Mintegral MCP server to the `mcpServers` configuration section:
```json
{
"mcpServers": {
"mintegral": {
"command": "npx",
"args": [ "-y", "@feedmob/mintegral-reporting" ]
"env": {
"MINTEGRAL_ACCESS_KEY": "your_mintegral_access_key",
"MINTEGRAL_API_KEY": "your_mintegral_api_key"
}
}
}
}
```
## Development
1. Clone the repository.
2. Navigate to the `src/mintegral-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variables (`MINTEGRAL_ACCESS_KEY` and `MINTEGRAL_API_KEY`).
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/src/inmobi-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Inmobi Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for Inmobi Reporting API.
## Features
This server provides the following tools:
* **`generate_inmobi_report_ids`**: Generates Inmobi report IDs for SKAN (iOS) and non-SKAN (Android) reports.
* **Input Parameters**:
* `startDate` (string, required): Start date for the report in `YYYY-MM-DD` format.
* `endDate` (string, required): End date for the report in `YYYY-MM-DD` format.
* **Output**: Returns JSON with `skanReportId` and `nonSkanReportId`.
* **`fetch_inmobi_report_data`**: Fetches data from Inmobi reports using report IDs.
* **Input Parameters**:
* `skanReportId` (string, required): SKAN report ID obtained from generate_inmobi_report_ids.
* `nonSkanReportId` (string, required): Non-SKAN report ID obtained from generate_inmobi_report_ids.
* **Output**: Returns the combined data from both reports.
## Setup
1. **Environment Variables**: Before running the server, you need to set the following environment variables:
```bash
export INMOBI_AUTH_URL='your_inmobi_auth_url'
export INMOBI_SKAN_REPORT_URL='your_inmobi_skan_report_url'
export INMOBI_NON_SKAN_REPORT_URL='your_inmobi_non_skan_report_url'
export INMOBI_REPORT_BASE_URL='your_inmobi_report_base_url'
export INMOBI_CLIENT_ID='your_inmobi_client_id'
export INMOBI_CLIENT_SECRET='your_inmobi_client_secret'
```
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the Inmobi MCP server to the `mcpServers` configuration section:
```json
{
"mcpServers": {
"inmobi": {
"command": "npx",
"args": [ "-y", "@feedmob/inmobi-reporting" ],
"env": {
"INMOBI_AUTH_URL": "your_inmobi_auth_url",
"INMOBI_SKAN_REPORT_URL": "your_inmobi_skan_report_url",
"INMOBI_NON_SKAN_REPORT_URL": "your_inmobi_non_skan_report_url",
"INMOBI_REPORT_BASE_URL": "your_inmobi_report_base_url",
"INMOBI_CLIENT_ID": "your_inmobi_client_id",
"INMOBI_CLIENT_SECRET": "your_inmobi_client_secret"
}
}
}
}
```
## Development
1. Clone the repository.
2. Navigate to the `src/inmobi-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variables.
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Model Context Protocol (MCP) Servers
This repository contains a collection of [MCP](https://modelcontextprotocol.io) servers developed and maintained by the FeedMob Development Team.
## 📚 Overview
MCP (Model Context Protocol) servers provide structured data and context to AI models, enabling them to better understand and interact with specific domains and data sources.
## 🚀 Available Servers
### Advertising & Marketing
- **[Jampp Reporting](src/jampp-reporting)** - Integration with Jampp's reporting API for advertising campaign data
- Campaign spend tracking
- Performance metrics
- Daily statistics
- **[Kayzen Reporting](src/kayzen-reporting)** - Integration with Kayzen's reporting API for advertising campaign data
- List all reports
- Get report's data
- **[Singular Reporting](src/singular-reporting)** - Integration with Singular's reporting API for advertising campaign data
- Getting reporting data from Singular API
- **[Appsamurai Reporting](src/appsamurai-reporting)** - Integration with AppSamurai Campaign Spend API for advertising campaign data
- Getting reporting data AppSamurai Campaign Spend API
- **[TapJoy Reporting](src/tapjoy-reporting/)** - Integration with TapJoy's reporting API for advertising campaign data
- Retrieves spend data for active advertiser ad sets within a specified date range
- **[Applovin Reporting](src/applovin-reporting/)** - Integration with Applovin's reporting API for advertising campaign data
- Retrieves spend data for active advertiser ad sets within a specified date range
- **[IronSource Reporting](src/ironsource-reporting/)** - Integration with IronSource's reporting API for advertising campaign data
- Retrieves spend data for active advertiser ad sets within a specified date range
- **[Mintegral Reporting](src/mintegral-reporting/)** - Integration with Mintegral's reporting API for advertising campaign data
- Retrieves spend data for active advertiser ad sets within a specified date range
- **[Inmobi Reporting](src/inmobi-reporting/)** - Integration with Inmobi's reporting API for advertising campaign data
- Retrieves spend data for active advertiser ad sets within a specified date range
- **[Liftoff Reporting](src/liftoff-reporting)** - Integration with Liftoff's reporting API for advertising campaign data
- Create reports
- Check report status
- Download report data (CSV or JSON)
- List apps
- List campaigns
- Download report data enriched with campaign names
## 📖 Documentation
Each server implementation includes its own detailed documentation in its respective directory.
## 🤝 Contributing
We welcome contributions! Please feel free to submit a Pull Request.
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
---
Developed and maintained with ❤️ by FeedMob Development Team
```
--------------------------------------------------------------------------------
/src/github-issues/README.md:
--------------------------------------------------------------------------------
```markdown
# FeedMob GitHub Issues MCP Server
A GitHub Issues MCP server customized for the FeedMob team
## Tools
### `search_issues`
- Search GitHub Issues through FeedMob API
- Input parameters:
- `scheam` (string, required): Get from system resource `issues/search_schema`
- `start_date` (string): Issue creation start date, defaults to 6 days ago
- `end_date` (string): Issue creation end date, defaults to today
- `status` (optional string): Issue status, e.g., 'open', 'closed'
- `repo` (optional string)
- `users` (optional string[]): List of users
- `team` (optional string): Team name, e.g., 'Star', 'Mighty'
- Returns: Search results
### `create_issue`
- Create a new Issue in a GitHub repository
- Input parameters:
- `owner` (optional string): Repository owner, can use environment variable default
- `repo` (string, required): Repository name
- `title` (string, required): Issue title
- `body` (optional string): Issue description
- `assignees` (optional string[]): Usernames to assign to
- `labels` (optional string[]): Labels to add
- `milestone` (optional number): Milestone number
- Returns: Created Issue details
### `update_issue`
- Update an existing Issue
- Input parameters:
- `owner` (string, required): Repository owner
- `repo` (string, required): Repository name
- `issue_number` (number, required): Issue number to update
- `title` (optional string): New title
- `body` (optional string): New description
- `state` (optional string): New state ('open' or 'closed')
- `labels` (optional string[]): New labels
- `assignees` (optional string[]): New assignees
- `milestone` (optional number): New milestone number
- Returns: Updated Issue details
### `get_issue`
- Get details of a specific Issue in a repository
- Input parameters:
- `owner` (string, required): Repository owner
- `repo` (string, required):
- `issue_number` (number, required): Issue number to retrieve
- Returns: GitHub Issue object and details
### `add_issue_comment`
- Add a comment to an existing Issue
- Input parameters:
- `owner` (string, required): Repository owner
- `repo` (string, required):
- `issue_number` (number, required): Issue number
- `body` (string, required): Comment content
- Returns: Created comment details
### `sync_latest_issues`
- Sync GitHub Issues Latest Data By FeedMob API
## Resources
### `issues/search_schema`
- Provides schema definition for Issues search
### Local Development
For local development, you can run the server using the following commands:
```bash
# Install dependencies
npm install
# Build project
npm run build
# Run server
npx tsx src/github-issues/index.ts
```
## License
This MCP server is licensed under the MIT License. This means you can freely use, modify, and distribute this software, but must comply with the terms and conditions of the MIT License. For more details, please refer to the LICENSE file in the project repository.
```
--------------------------------------------------------------------------------
/src/samsung-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Samsung Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for Samsung API.
## Features
This server provides the following tools:
* **`get_samsung_content_metrics`**: Fetch content metrics from Samsung API for a specific date range.
* **Input Parameters**:
* `startDate` (string, required): Start date for the report in `YYYY-MM-DD` format.
* `endDate` (string, required): End date for the report in `YYYY-MM-DD` format.
* `metricIds` (array of strings, optional): Optional array of metric IDs to fetch. Defaults to standard metrics if not provided:
* `total_unique_installs_filter`
* `revenue_total`
* `revenue_iap_order_count`
* `daily_rat_score`
* `daily_rat_volumne`
* **Output**: Returns the content metrics data as JSON.
## Setup
1. **Environment Variables**: Before running the server, you need to set the following environment variables:
```bash
export SAMSUNG_ISS='your_samsung_issuer'
export SAMSUNG_PRIVATE_KEY='your_samsung_private_key'
export SAMSUNG_CONTENT_ID='your_samsung_content_id'
```
**Required Environment Variables:**
* `SAMSUNG_ISS`: Samsung issuer identifier for JWT authentication
* `SAMSUNG_PRIVATE_KEY`: Private key for JWT signing (RS256 algorithm)
* `SAMSUNG_CONTENT_ID`: Content ID for which to fetch metrics
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the Samsung MCP server to the `mcpServers` configuration section:
```json
{
"mcpServers": {
"samsung": {
"command": "npx",
"args": [ "-y", "@feedmob/samsung-reporting" ],
"env": {
"SAMSUNG_ISS": "your_samsung_issuer",
"SAMSUNG_PRIVATE_KEY": "your_samsung_private_key",
"SAMSUNG_CONTENT_ID": "your_samsung_content_id"
}
}
}
}
```
## Authentication
The server uses JWT (JSON Web Token) authentication with RS256 algorithm to authenticate with Samsung API:
1. Generates a JWT token using the provided private key and issuer
2. Uses the JWT to obtain an access token from Samsung's auth endpoint
3. Uses the access token for subsequent API calls to fetch content metrics
The JWT token includes:
* `iss`: Samsung issuer identifier
* `scopes`: `['publishing', 'gss']`
* `exp`: Token expiration (20 minutes from issue time)
* `iat`: Token issue time
## Development
1. Clone the repository.
2. Navigate to the `src/samsung-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variables (`SAMSUNG_ISS`, `SAMSUNG_PRIVATE_KEY`, and `SAMSUNG_CONTENT_ID`).
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/src/ironsource-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# IronSource Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for IronSource Reporting API for advertisers.
## Features
This server provides the following tool:
* **`get_advertiser_report_from_ironsource`**: Retrieves campaign spending data from IronSource Reporting API for advertisers.
* **Input Parameters**:
* `startDate` (string, required): The start date for the report in `YYYY-MM-DD` format.
* `endDate` (string, required): The end date for the report in `YYYY-MM-DD` format.
* `metrics` (string, optional): Comma-separated list of metrics to include (default: 'impressions,clicks,completions,installs,spend').
* `breakdowns` (string, optional): Comma-separated list of breakdowns (default: 'day,campaign').
* `format` (string, optional): Format of the report data ('json' or 'csv'). Default is 'json'.
* `count` (number, optional): Number of records to return (default: 10000, max: 250000).
* `campaignId` (string, optional): Filter by comma-separated list of campaign IDs.
* `bundleId` (string, optional): Filter by comma-separated list of bundle IDs.
* `creativeId` (string, optional): Filter by comma-separated list of creative IDs.
* `country` (string, optional): Filter by comma-separated list of countries (ISO 3166-2).
* `os` (string, optional): Filter by operating system (ios or android).
* `deviceType` (string, optional): Filter by device type (phone or tablet).
* `adUnit` (string, optional): Filter by ad unit type (e.g., 'rewardedVideo,interstitial').
* `order` (string, optional): Order results by breakdown/metric.
* `direction` (string, optional): Order direction ('asc' or 'desc'). Default is 'asc'.
* **Output**: Returns the report data in the specified format (JSON or CSV).
## Setup
1. **Environment Variables**: Before running the server, you need to set the following environment variables:
```bash
export IRONSOURCE_SECRET_KEY='your_ironsource_secret_key'
export IRONSOURCE_REFRESH_TOKEN='your_ironsource_refresh_token'
```
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the IronSource MCP server to the `mcpServers` configuration section:
```json
{
"mcpServers": {
"ironsource": {
"command": "npx",
"args": [ "-y", "@feedmob/ironsource-reporting" ]
"env": {
"IRONSOURCE_SECRET_KEY": "your_ironsource_secret_key",
"IRONSOURCE_REFRESH_TOKEN": "your_ironsource_refresh_token"
}
}
}
}
```
## Development
1. Clone the repository.
2. Navigate to the `src/ironsource-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variables (`IRONSOURCE_SECRET_KEY` and `IRONSOURCE_REFRESH_TOKEN`).
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/src/ironsource-aura-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# IronSource Aura Reporting MCP Server
Node.js server implementing Model Context Protocol (MCP) for IronSource Aura Reporting API for advertisers.
## Features
This server provides the following tool:
* **`get_advertiser_report_from_aura`**: Retrieves campaign spending data from Aura(IronSource) Reporting API for advertisers.
* **Input Parameters**:
* `startDate` (string, required): The start date for the report in `YYYY-MM-DD` format.
* `endDate` (string, required): The end date for the report in `YYYY-MM-DD` format.
* `metrics` (string, optional): Comma-separated list of metrics to include (default: 'impressions,clicks,completions,installs,spend').
* `breakdowns` (string, optional): Comma-separated list of breakdowns (default: 'day,campaign_name').
* `format` (string, optional): Format of the report data ('json' or 'csv'). Default is 'json'.
* `count` (number, optional): Number of records to return (default: 10000, max: 250000).
* `campaignId` (string, optional): Filter by comma-separated list of campaign IDs.
* `bundleId` (string, optional): Filter by comma-separated list of bundle IDs.
* `creativeId` (string, optional): Filter by comma-separated list of creative IDs.
* `country` (string, optional): Filter by comma-separated list of countries (ISO 3166-2).
* `os` (string, optional): Filter by operating system (ios or android).
* `deviceType` (string, optional): Filter by device type (phone or tablet).
* `adUnit` (string, optional): Filter by ad unit type (e.g., 'rewardedVideo,interstitial').
* `order` (string, optional): Order results by breakdown/metric.
* `direction` (string, optional): Order direction ('asc' or 'desc'). Default is 'asc'.
* **Output**: Returns the report data in the specified format (JSON or CSV).
## Setup
1. **Environment Variables**: Before running the server, you need to set the following environment variables:
```bash
export IRONSOURCE_AURA_API_KEY='your_ironsource_aura_api_key'
export IRONSOURCE_AURA_API_BASE_URL='https://api_base_url'
```
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
* macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
* Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the IronSource Aura MCP server to the `mcpServers` configuration section:
```json
{
"mcpServers": {
"ironsource-aura": {
"command": "npx",
"args": [ "-y", "@feedmob/ironsource-aura-reporting" ],
"env": {
"IRONSOURCE_AURA_API_KEY": "your_ironsource_aura_api_key",
"IRONSOURCE_AURA_API_BASE_URL": "your_api_base_url"
}
}
}
}
```
## Development
1. Clone the repository.
2. Navigate to the `src/ironsource-aura-reporting` directory.
3. Install dependencies: `npm install`
4. Set the required environment variables (`IRONSOURCE_AURA_API_KEY` and `IRONSOURCE_AURA_API_BASE_URL`).
5. Build the project: `npm run build`
6. Run the server directly: `node dist/index.js`
## License
MIT
```
--------------------------------------------------------------------------------
/src/civitai-records/README.md:
--------------------------------------------------------------------------------
```markdown
# FeedMob Civitai Records MCP Server
MCP server for managing Civitai content workflows including prompts, assets (images/videos), and publication records. Tracks the full lifecycle from content generation to Civitai publication with support for many-to-many associations between posts, assets, and prompts.
## Prerequisites
- Node.js 18+
- npm 9+
## Installation
```bash
npm install
```
Run the command inside `src/civitai-records/`. Dependencies are local to this package.
## Local Database
Launch the Postgres container defined in `docker-compose.yml` to apply the init scripts under `infra/db-init`:
```bash
docker compose up -d db
```
Once the container finishes seeding, connect with the seeded sample login (`richard`) to verify access:
```bash
PGPASSWORD=richard_password psql -h localhost -U richard -d civitai
```
Inside the session set the schema search path so you only query the `civitai` schema:
```sql
SET search_path TO civitai;
```
## Available Scripts
- `npm run dev` — start the server with `tsx` for hot reload during development.
- `npm run dev:cli` — enter the FastMCP interactive development CLI.
- `npm run inspect` — open the FastMCP inspector to exercise the server’s tools.
- `npm run build` — compile TypeScript to `dist/` using the shared compiler defaults.
- `npm run start` — execute the compiled server from `dist/server.js` for smoke testing.
## Tools
### Prompt Management
- `create_prompt` — Create a new prompt record with content, model info, purpose, and metadata.
### Asset Management
- `create_asset` — Create a new asset (image/video) record with URI, source, optional input/output prompt associations, and metadata.
- `update_asset_prompt` — Update the input or output prompt association for an existing asset.
### Civitai Post Management
- `create_civitai_post` — Record a new Civitai publication with status, asset reference, title, description, and metadata.
- `update_civitai_post_asset` — Update the primary asset association for an existing Civitai post.
- `create_post_association` — Create many-to-many associations between Civitai posts and assets or prompts.
- `list_civitai_posts` — Query Civitai posts with filtering by civitai_id, asset_id, asset_type, status, created_by, or time range. Supports pagination and optional detailed asset/prompt inclusion.
- `fetch_civitai_post_assets` — Retrieve the live media assets (images/videos) for a specific post directly from the public Civitai Images API without persisting them. Accepts the Civitai post ID plus pagination controls and returns engagement stats (likes, hearts, comments, etc.). Use this to inspect performance or get the Civitai media assets (Civitai images) for the post.
### Environment Variables
Copy `env.sample` to `.env` if you need local configuration:
```bash
cp env.sample .env
```
Add any required secrets as you build out the integration. Currently required:
- `DATABASE_URL` — PostgreSQL connection string for the Civitai records database (format: `postgres://user:password@host:5432/database`).
- `CIVITAI_ACCOUNT` — Civitai account username for post attribution (default: `c29`).
The server automatically loads `.env` via `dotenv` for all npm scripts.
## Usage with Claude Desktop
Add the server to your Claude configuration to expose the tools to Claude:
```json
{
"mcpServers": {
"feedmob-civitai-records": {
"command": "npx",
"args": ["-y", "@feedmob/civitai-records"],
"transport": "stdio",
"env": {
"DATABASE_URL": "postgres://user:password@host:5432/database",
"CIVITAI_ACCOUNT": "c29"
}
}
}
}
```
Adjust the `args` if you run from a local path or different entry point.
## Development Notes
- Implement new tooling in `src/tools/` and register each tool via `src/server.ts`.
- Protect external inputs with `zod` schemas and emit helpful error messages.
- Co-locate tests beside the code (for example, `src/tools/__tests__/records.test.ts`) and wire `npm test` once coverage exists.
- Load secrets from environment variables instead of hardcoding them.
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/README.md:
--------------------------------------------------------------------------------
```markdown
# FeedMob Direct Spend Visualizer – n8n Nodes
These community nodes wrap the FeedMob Claude Agent plugin (“direct spend visualizer”) so that n8n workflows can trigger Claude via AWS Bedrock, run FeedMob’s MCP-aware skill, and emit chart-ready direct spend insights without writing custom code.
## Requirements
1. `npm install` automatically clones [`feed-mob/claude-code-marketplace`](https://github.com/feed-mob/claude-code-marketplace) into `vendor/claude-code-marketplace`. Ensure `git` is on the host; rerun `npm install` (or `node scripts/setup-plugin.js`) anytime you need the latest plugin changes.
2. FeedMob MCP credentials (`FEEDMOB_KEY`, `FEEDMOB_SECRET`, `FEEDMOB_API_BASE`) are required. Generate keys at [FeedMob Admin → API Keys](https://admin.feedmob.com/api_keys).
3. Supply AWS Bedrock credentials with access to Anthropic Claude models (`AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`). The node sets `CLAUDE_CODE_USE_BEDROCK=1` and uses your configured model IDs.
## Local development
Follow the official n8n guide: https://docs.n8n.io/integrations/creating-nodes/test/run-node-locally/
### Steps
1. **Install n8n globally**
```bash
npm install n8n -g
```
2. **Build and link this node**
```bash
cd src/n8n-nodes-feedmob-direct-spend-visualizer
npm install
npm run build
npm link
```
3. **Link the node into your local n8n installation**
```bash
# Navigate to your n8n custom nodes directory
# e.g., ~/.n8n/custom or ~/.n8n/<CUSTOM_NAME> if N8N_CUSTOM_EXTENSIONS is set
cd ~/.n8n/custom
npm link @feedmob/n8n-nodes-feedmob-direct-spend-visualizer
```
> **Note:** If `~/.n8n/custom` doesn't exist, create it manually and run `npm init -y` before linking.
4. **Start n8n**
```bash
n8n start
```
5. **Test the node**
Open http://localhost:5678 and search for "Direct Spend Visualizer" in the node panel (search by node name, not package name).
Ensure AWS and FeedMob credentials are available in the n8n process environment for the Claude Agent SDK to authenticate properly.
## Installing inside n8n
* **n8n Cloud / UI** – Settings → Community Nodes → Install → enter `@feedmob/n8n-nodes-feedmob-direct-spend-visualizer`.
* **Self-hosted** – include `N8N_COMMUNITY_PACKAGES="@feedmob/n8n-nodes-feedmob-direct-spend-visualizer"` in your process / container env variables.
Make sure community nodes are allowed for your workspace before installing.
## Credentials
Create credentials of type **FeedMob Direct Spend Visualizer** and fill the following fields:
| Field | Description |
| --- | --- |
| AWS Region | Defaults to `us-east-1`. Region used for Bedrock Claude. |
| AWS Access Key ID | Required. Must have permissions to invoke Anthropic models via Bedrock. |
| AWS Secret Access Key | Required. Secret for the above key. |
| FeedMob Key | Required. Exported to `FEEDMOB_KEY` for the plugin’s MCP server (create via https://admin.feedmob.com/api_keys). |
| FeedMob Secret | Required. Exported to `FEEDMOB_SECRET` (create via https://admin.feedmob.com/api_keys). |
| FeedMob API Base | Required. Exported to `FEEDMOB_API_BASE`. |
| Anthropic Model (primary) | Defaults to `us.anthropic.claude-sonnet-4-20250514-v1:0`. |
| Anthropic Model (fast) | Defaults to `us.anthropic.claude-3-5-haiku-20241022-v1:0`. |
## Supported operations
| Operation | Description |
| --- | --- |
| **Visualize Spend** | Provide `start_date`, `end_date`, and `click_url_id`. The node loads `vendor/claude-code-marketplace/plugins/direct-spend-visualizer`, starts the Claude Agent SDK (with `allowedTools: ["Skill","mcp__plugin_direct-spend-visualizer_feedmob__get_direct_spends"]`), and returns the skill’s ASCII chart plus JSON (status, summary, data). |
## Troubleshooting tips
* The plugin is bundled under `vendor/claude-code-marketplace/plugins/direct-spend-visualizer`. If it’s missing or out of date, rerun `npm install` or execute `node scripts/setup-plugin.js`.
* The Agent SDK call whitelists the FeedMob MCP tool (`mcp__plugin_direct-spend-visualizer_feedmob__get_direct_spends`) so it can access the API without prompting. Ensure your FeedMob credentials are valid or the tool will fail with a permission error.
* Max turns default to 50 to give Claude enough room to fetch data, render ASCII charts, and format the JSON output. Lower this in the node settings if you need faster runs.
## Scripts
* `npm run build` – compile TypeScript and copy the SVG icon into `dist/`.
* `npm run dev` – run TypeScript in watch mode during development.
* `node scripts/setup-plugin.js` – reclone or refresh the FeedMob plugin marketplace (normally run for you during `npm install`).
## License
MIT
```
--------------------------------------------------------------------------------
/src/sensor-tower-reporting/README.md:
--------------------------------------------------------------------------------
```markdown
# Sensor Tower Reporting MCP Server
This MCP server provides tools to interact with the [Sensor Tower API](https://sensortower.com/api) for mobile app intelligence and market data.
## Features
- **Tools**:
- [`get_app_metadata`](src/index.ts:929): Fetch app metadata including name, publisher, categories, description, screenshots, and ratings
- [`get_top_in_app_purchases`](src/index.ts:981): Fetch top in-app purchases for iOS apps
- [`get_compact_sales_report_estimates`](src/index.ts:1030): Get download and revenue estimates in compact format (revenues in cents)
- [`get_active_users`](src/index.ts:1119): Fetch active user estimates (DAU/WAU/MAU) by country and date
- [`get_category_history`](src/index.ts:1194): Get detailed category ranking history for apps
- [`get_category_ranking_summary`](src/index.ts:1274): Fetch today's category ranking summary
- [`get_network_analysis`](src/index.ts:1329): Get impressions share of voice (SOV) time series
- [`get_network_analysis_rank`](src/index.ts:1405): Fetch network analysis ranking data
- [`get_retention`](src/index.ts:1481): Get app retention data (day 1 to day 90)
- [`get_downloads_by_sources`](src/index.ts:1551): Fetch app downloads by sources (organic, paid, browser)
## Setup
1. **Install dependencies**:
```bash
npm install
```
2. **Configure environment variables**:
Create a `.env` file with the following variables:
```env
AUTH_TOKEN=your_sensor_tower_auth_token
SENSOR_TOWER_BASE_URL=https://api.sensortower.com
```
3. **Build the server**:
```bash
npm run build
```
## Running the Server
To run the server directly for testing:
```bash
npm start
```
For development with hot reload:
```bash
npm run dev
```
## Usage with Claude Desktop
1. Make sure you have installed and updated to the latest version of Claude for Desktop.
2. Open the Claude for Desktop configuration file:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
3. Add the Sensor Tower Reporting MCP server to the configuration:
### NPX
```json
{
"mcpServers": {
"sensor-tower-reporting": {
"command": "npx",
"args": ["-y", "@feedmob/sensor-tower-reporting"],
"env": {
"AUTH_TOKEN": "your_sensor_tower_auth_token",
"SENSOR_TOWER_BASE_URL": "https://api.sensortower.com"
}
}
}
}
```
### Local Development
```json
{
"mcpServers": {
"sensor-tower-reporting": {
"command": "node",
"args": ["dist/index.js"],
"cwd": "/path/to/sensor-tower-reporting",
"env": {
"AUTH_TOKEN": "your_sensor_tower_auth_token",
"SENSOR_TOWER_BASE_URL": "https://api.sensortower.com"
}
}
}
}
```
## API Reference
### App Metadata
- **Platform Support**: iOS, Android
- **Limits**: Maximum 100 app IDs per request
- **Returns**: App name, publisher, categories, description, screenshots, ratings
### Sales & Revenue Data
- **Compact Sales Report**: Download and revenue estimates with flexible filtering
- **Revenue Format**: All revenues returned in cents
- **Filtering**: By app IDs, publisher IDs, unified IDs, or categories
### User Analytics
- **Active Users**: DAU/WAU/MAU estimates by country and time period
- **Retention**: Day 1 to day 90 retention rates with baseline comparison
- **Limits**: Maximum 500 app IDs for active users and retention
### Ranking & Category Data
- **Category History**: Detailed ranking history by category and chart type
- **Category Summary**: Current ranking summary for specific apps
- **Hourly Rankings**: Available for iOS apps
### Advertising Intelligence
- **Network Analysis**: Share of voice (SOV) for advertising networks
- **Network Rankings**: Ranking data across countries and networks
- **Downloads by Sources**: Organic vs paid download attribution
## Error Handling
The server includes comprehensive error handling with specific error types:
- [`ConfigurationError`](src/index.ts:36): Missing or invalid configuration
- [`SensorTowerApiError`](src/index.ts:29): API-related errors with detailed messages
- Input validation using [Zod schemas](src/index.ts:836) for all parameters
## Environment Variables
| Variable | Required | Description | Default |
|----------|----------|-------------|---------|
| `AUTH_TOKEN` | Yes | Sensor Tower API authentication token | - |
| `SENSOR_TOWER_BASE_URL` | No | Sensor Tower API base URL | `https://api.sensortower.com` |
## Development
### Scripts
- `npm run build`: Compile TypeScript and make executables
- `npm run watch`: Watch mode for development
- `npm run prepare`: Prepare package for publishing
### Testing
Use the MCP inspector for manual testing:
```bash
npm run inspect
```
## License
MIT
```
--------------------------------------------------------------------------------
/src/imagekit/README.md:
--------------------------------------------------------------------------------
```markdown
# FeedMob ImageKit MCP Server
Lightweight Model Context Protocol server that exposes ImageKit-related tooling via the FeedMob internal MCP stack. The starter implementation currently publishes a single arithmetic helper and is intended as a template for richer ImageKit automation.
## Prerequisites
- Node.js 18+
- npm 9+
## Installation
```bash
npm install
```
Run the command inside `src/imagekit/`. Dependencies are local to this package.
## Available Scripts
- `npm run dev` — start the server with `tsx` for hot reload during development.
- `npm run dev:cli` — enter the FastMCP interactive development CLI.
- `npm run inspect` — open the FastMCP inspector to exercise the server’s tools.
- `npm run build` — compile TypeScript to `dist/` using the shared root `tsconfig.json`.
- `npm run start` — execute the compiled server from `dist/server.js` for smoke testing.
## Tools
- `crop_and_watermark_image` — calls the Comet Images API to crop an input image to a supported aspect ratio, optionally adds a watermark, and returns the final image URL (ImageKit URL when uploads are enabled, otherwise the generated link).
- `upload_file` — uploads an asset to ImageKit (default provider) using base64 content, a local filesystem path, or a remote URL and returns the resulting links. Files land in the `upload/` folder and include the `upload` tag unless you override those values.
### Environment Variables
#### ImageKit Configuration
- `IMAGEKIT_PRIVATE_KEY` — required for the `upload_file` tool and enables automatic ImageKit uploads from `crop_and_watermark_image`.
- **Docs**: https://imagekit.io/docs/api-keys
- Please copy the Private Key
#### Image Generation Provider (Comet API)
- `IMAGE_TOOL_API_KEY` — required for `crop_and_watermark_image`.
- **Docs**: https://api.cometapi.com/doc
- Create a new API key
- `IMAGE_TOOL_BASE_URL` — optional override for the image-generation provider base URL; defaults to `https://api.cometapi.com/v1`.
- `IMAGE_TOOL_MODEL_ID` — optional model identifier; defaults to `bytedance-seedream-4-0-250828`.
- You can switch to another Doubao model listed at https://api.cometapi.com/pricing
#### Alternative Provider: Volcengine Ark (火山方舟)
- `IMAGE_TOOL_BASE_URL` — use `https://ark.cn-beijing.volces.com/api/v3` (change region if needed)
- `IMAGE_TOOL_API_KEY` — get your API key at https://console.volcengine.com/
- `IMAGE_TOOL_MODEL_ID` — recommended: `doubao-seedream-4-0-250828`
- You can switch to another Doubao model if needed. Find more models at https://www.volcengine.com/docs/82379/1541523
Copy `env.sample` to `.env` when developing locally:
```bash
cp env.sample .env
# then edit with your API key
```
The server automatically loads `.env` via `dotenv` when you run any npm script.
#### Provider Example (Volcengine Ark / 火山方舟)
Override the defaults if you want to target Volcengine Ark:
```
IMAGE_TOOL_BASE_URL=https://ark.cn-beijing.volces.com/api/v3
IMAGE_TOOL_API_KEY=your-ark-api-key
IMAGE_TOOL_MODEL_ID=doubao-seedream-4-0-250828
```
### Example Invocation
From the FastMCP inspector:
```
> crop_and_watermark_image
? imageUrl https://example.com/image.png
? aspectRatio 16:9
? watermarkText FeedMob Confidential
```
The tool returns a single URL string pointing to the resulting asset. When ImageKit credentials are configured, the URL references the uploaded asset in ImageKit; otherwise it references the link returned by the generation API.
Upload example:
```
> upload_file
? provider imagekit
? file ./assets/banner.png
? fileName banner.png
? folder /marketing/campaign-2025
```
The tool responds with a JSON summary plus resource links for the uploaded asset and its thumbnail (when provided by ImageKit).
Remote content upload remains supported by supplying the `file` parameter with a remote URL:
```
> upload_file
? provider imagekit
? file https://ik.imagekit.io/demo/sample.jpg
? fileName sample.jpg
```
Unless overridden via the `options` object, uploads default to `useUniqueFileName: true` (avoid filename collisions) and `isPrivateFile: false` (serve public URLs).
## Usage with Claude Desktop
Add the server to your Claude configuration to make the tools available to the assistant:
```json
{
"mcpServers": {
"feedmob-imagekit": {
"command": "npx",
"args": ["-y", "@feedmob/imagekit"],
"transport": "stdio",
"env": {
"IMAGE_TOOL_API_KEY": "your-image-tool-api-key",
"IMAGEKIT_PRIVATE_KEY": "your-imagekit-private-key"
}
}
}
}
```
Adjust the `args` to match your installation method (local path, published package, or ts-node entry point).
The server defaults to the Comet Images API (`https://api.cometapi.com/v1`) and bundled model (`bytedance-seedream-4-0-250828`). Optional overrides: add `IMAGE_TOOL_BASE_URL` and `IMAGE_TOOL_MODEL_ID` to the `env` block only when you need to swap API endpoints or models.
Volcengine Ark / 火山方舟 example:
```json
{
"mcpServers": {
"feedmob-imagekit": {
"command": "npx",
"args": ["-y", "@feedmob/imagekit"],
"transport": "stdio",
"env": {
"IMAGE_TOOL_API_KEY": "your-ark-api-key",
"IMAGE_TOOL_BASE_URL": "https://ark.cn-beijing.volces.com/api/v3",
"IMAGE_TOOL_MODEL_ID": "doubao-seedream-4-0-250828",
"IMAGEKIT_PRIVATE_KEY": "your-imagekit-private-key"
}
}
}
}
```
## Development Notes
- Implement new tooling in `src/tools/` and register it through `src/server.ts`; guard external inputs with `zod` schemas.
- Co-locate any tests alongside the code (for example, `src/__tests__/upload.test.ts`) and wire `npm test` when the suite exists.
- Keep environment-specific values outside the codebase; reach for `.env` files consumed via `dotenv` if future tools require credentials.
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/AGENTS.md:
--------------------------------------------------------------------------------
```markdown
# n8n FeedMob Direct Spend Visualizer – Agent Guidelines
## Build & Development Commands
- `npm install` – installs dependencies and clones the Claude plugin via postinstall hook
- `npm run build` – compiles TypeScript (target ES2020, CommonJS) and copies assets to `dist/`
- `npm run dev` – runs TypeScript compiler in watch mode for local development
- `npm run prepare` – runs build automatically before publishing
- No test suite present; manually verify by linking to local n8n (`npm link`) and testing workflows
## Code Style & Structure
- **TypeScript**: Strict mode, ES2020 target, CommonJS modules, declaration files emitted to `dist/`
- **Imports**: Use named imports from `n8n-workflow` and `@anthropic-ai/claude-agent-sdk`; Node built-ins (`fs`, `path`) imported at top
- **Naming**: `camelCase` for variables/functions, `PascalCase` for classes/types, `SCREAMING_SNAKE_CASE` for constants
- **Types**: Define explicit types for credentials and result shapes; use `type` for object shapes, `INodeType` and `INodeTypeDescription` from n8n-workflow
- **Error Handling**: Throw descriptive errors with context (e.g., missing credentials, plugin path not found); support `continueOnFail` for batch processing
- **Functions**: Extract reusable logic into private functions (`extractAssistantText`, `normalizeResult`, `tryParseJson`, `buildRuntimeEnv`, `resolvePluginPath`)
- **Formatting**: Two-space indentation, semicolons required, `esModuleInterop` enabled
## n8n Node Conventions
- Place node classes in `nodes/*/` and credential classes in `credentials/`; export from `index.ts`
- Node `description` object defines UI fields (`displayName`, `properties`, `credentials`)
- `execute()` method processes `INodeExecutionData[]` input items and returns results array
- Use `this.getNodeParameter()` and `this.getCredentials()` to access user inputs
- Environment variables passed to child processes via `buildRuntimeEnv()` helper
```
--------------------------------------------------------------------------------
/AGENTS.md:
--------------------------------------------------------------------------------
```markdown
# Repository Guidelines
## Project Structure & Module Organization
This monorepo hosts standalone MCP servers under `src/<integration-name>`. Each package keeps its own `package.json`, TypeScript sources in `src/`, and emits `dist/` when built. Shared compiler defaults live in the root `tsconfig.json`. Keep utilities local to the package that owns them and document integration specifics in that package’s README. Add tests beside the code to keep each server self-contained.
## Build, Test, and Development Commands
Install dependencies inside the package you touch (for example, `cd src/imagekit && npm install`). Common scripts:
- `npm run dev` — runs `tsx src/server.ts` with hot reload.
- `npm run inspect` — opens `fastmcp inspect` for manual tool trials.
- `npm run build` — compiles to `dist` and marks CLIs executable.
- `npm run start` — executes the compiled output for smoke checks.
Always run `npm run build` before committing so published artifacts stay up to date.
## Coding Style & Naming Conventions
Write TypeScript targeting ES2022/NodeNext with strict mode. Use two-space indentation, `camelCase` for variables and functions, `PascalCase` for types, and favour `const`. Validate untrusted input with `zod` schemas and surface actionable error messages. Load secrets through environment variables and mention required keys in the package README.
Respect the Single Responsibility Principle. When an implementation starts doing more than one thing, pull supporting logic into small private helpers (preferred) or, if multiple packages need it, a clearly named shared module. This keeps public surfaces focused and easier to test.
## Testing Guidelines
Automate coverage for new logic. Create co-located specs such as `src/tools/__tests__/upload.test.ts` and wire `npm test` to your chosen runner (Vitest or Node’s built-in test module). Stub external HTTP calls to avoid hitting partner APIs. Before opening a PR, run `npm run build && npm test` in every package you touched and note any manual checks performed.
## Commit & Pull Request Guidelines
Prefer present-tense, scoped commits like `feat(imagekit): add signed-url tool`; keep unrelated changes in separate commits. Pull requests should summarise the integration affected, link to Jira or GitHub issues, list required env vars, and attach screenshots or CLI transcripts when behaviour shifts. Request review from an engineer familiar with the server and wait for green checks before merging.
## Security & Configuration Tips
Never commit secrets. Use `.env` files loaded via `dotenv`, rotate credentials when rolling keys, and redact sensitive output from logs and pull requests.
```
--------------------------------------------------------------------------------
/src/civitai-records/src/lib/prisma.ts:
--------------------------------------------------------------------------------
```typescript
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();
```
--------------------------------------------------------------------------------
/src/n8n-nodes-sensor-tower/index.ts:
--------------------------------------------------------------------------------
```typescript
export * from './nodes/SensorTower/SensorTower.node';
export * from './credentials/SensorTowerApi.credentials';
```
--------------------------------------------------------------------------------
/src/femini-reporting/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci && npm run build
EXPOSE 3002
CMD ["node", "dist/index.js"]
```
--------------------------------------------------------------------------------
/src/user-activity-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
```
--------------------------------------------------------------------------------
/.claude/settings.local.json:
--------------------------------------------------------------------------------
```json
{
"permissions": {
"allow": [
"mcp__zen__chat",
"Bash(npm run build)",
"mcp__zen__analyze"
],
"deny": []
}
}
```
--------------------------------------------------------------------------------
/src/applovin-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/appsamurai-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/feedmob-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/femini-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/impact-radius-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/inmobi-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/ironsource-aura-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/ironsource-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/jampp-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/kayzen-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/liftoff-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/mintegral-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/rtb-house-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/samsung-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/sensor-tower-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/singular-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/smadex-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/tapjoy-reporting/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/work-journals/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
},
"include": [
"./src/**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/index.ts:
--------------------------------------------------------------------------------
```typescript
export * from './nodes/FeedmobDirectSpendVisualizer/FeedmobDirectSpendVisualizer.node';
export * from './credentials/FeedmobDirectSpendVisualizerApi.credentials';
```
--------------------------------------------------------------------------------
/src/github-issues/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "."
},
"include": [
"./**/*.ts"
]
}
```
--------------------------------------------------------------------------------
/src/github-issues/common/version.ts:
--------------------------------------------------------------------------------
```typescript
// If the format of this file changes, so it doesn't simply export a VERSION constant,
// this will break .github/workflows/version-check.yml.
export const VERSION = "0.0.5";
```
--------------------------------------------------------------------------------
/src/n8n-nodes-sensor-tower/nodes/SensorTower/logo.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="#2CDBA6"/>
<rect x="45" y="25" width="10" height="50" rx="2" fill="#0F8C6A"/>
<circle cx="50" cy="25" r="6" fill="#0F8C6A"/>
</svg>
```
--------------------------------------------------------------------------------
/src/civitai-records/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
```
--------------------------------------------------------------------------------
/src/imagekit/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/03_init.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 03_init.sql
-- Seed / create example users using the helper
-- (Change passwords before real use)
-- =========================================================
SELECT civitai.create_app_user('richard', 'richard_password');
SELECT civitai.create_app_user('alice', 'alice_password');
```
--------------------------------------------------------------------------------
/src/civitai-records/build.sh:
--------------------------------------------------------------------------------
```bash
#!/bin/bash
# Exit on error
set -e
echo "🔨 Building civitai-records..."
# Generate Prisma client
echo "📦 Generating Prisma client..."
prisma generate
# Compile TypeScript
echo "🔧 Compiling TypeScript..."
tsc
# Copy markdown documentation
echo "📄 Copying markdown documentation..."
mkdir -p dist/prompts
cp src/prompts/*.md dist/prompts/
echo "✅ Build complete!"
```
--------------------------------------------------------------------------------
/src/civitai-records/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
services:
db:
image: postgres:16
container_name: civitai-db
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: civitai
ports:
- "5432:5432"
volumes:
- ./infra/db-init:/docker-entrypoint-initdb.d
- civitai_data:/var/lib/postgresql/data
volumes:
civitai_data:
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "Node",
"declaration": true,
"outDir": "dist",
"rootDir": ".",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"nodes/**/*.ts",
"credentials/**/*.ts",
"index.ts",
"types.d.ts"
]
}
```
--------------------------------------------------------------------------------
/src/n8n-nodes-sensor-tower/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "Node",
"declaration": true,
"outDir": "dist",
"rootDir": ".",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"nodes/**/*.ts",
"credentials/**/*.ts",
"index.ts",
"types.d.ts"
]
}
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/13_add_columns_to_asset_stats.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 13_add_columns_to_asset_stats.sql
-- Add civitai_created_at, civitai_account, post_id, and on_behalf_of columns to asset_stats
-- =========================================================
SET ROLE civitai_owner;
ALTER TABLE civitai.asset_stats
ADD COLUMN post_id bigint REFERENCES civitai.civitai_posts(id),
ADD COLUMN civitai_created_at timestamptz,
ADD COLUMN civitai_account text,
ADD COLUMN on_behalf_of text;
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/nodes/FeedmobDirectSpendVisualizer/logo.svg:
--------------------------------------------------------------------------------
```
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#08BFB0"/>
<stop offset="100%" stop-color="#0560A4"/>
</linearGradient>
</defs>
<rect x="6" y="6" width="84" height="84" rx="18" fill="#041F2F"/>
<path d="M18 66h12v16H18zM36 52h12v30H36zM54 40h12v42H54zM72 24h12v58H72z" fill="url(#grad)"/>
<circle cx="30" cy="34" r="6" fill="#FCB045"/>
<circle cx="60" cy="22" r="5" fill="#FCB045"/>
</svg>
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/10_update_on_behalf_of_for_existing_records.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 10_update_on_behalf_of_for_existing_records.sql
-- =========================================================
SET ROLE civitai_owner;
-- Backfill existing rows: set on_behalf_of to created_by for historical data
UPDATE civitai.prompts SET on_behalf_of = created_by WHERE on_behalf_of IS NULL;
UPDATE civitai.assets SET on_behalf_of = created_by WHERE on_behalf_of IS NULL;
UPDATE civitai.civitai_posts SET on_behalf_of = created_by WHERE on_behalf_of IS NULL;
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/civitai-records/src/prompts/recordCivitaiWorkflow.ts:
--------------------------------------------------------------------------------
```typescript
import * as fs from "fs";
import * as path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const recordCivitaiWorkflowPrompt = {
name: "record_civitai_workflow",
description: "Guide for properly recording Civitai posts, prompts, and assets in the correct order",
load: async () => {
const workflowContent = await fs.promises.readFile(
path.join(__dirname, "record-civitai-workflow.md"),
"utf-8"
);
return workflowContent;
},
};
```
--------------------------------------------------------------------------------
/src/n8n-nodes-sensor-tower/credentials/SensorTowerApi.credentials.ts:
--------------------------------------------------------------------------------
```typescript
import type { INodeProperties } from 'n8n-workflow';
export class SensorTowerApi {
name = 'sensorTowerApi';
displayName = 'Sensor Tower API';
properties: INodeProperties[] = [
{
displayName: 'Base URL',
name: 'baseUrl',
type: 'string',
default: 'https://api.sensortower.com',
description: 'Base URL for Sensor Tower API',
required: true,
},
{
displayName: 'Auth Token (AUTH_TOKEN)',
name: 'authToken',
type: 'string',
typeOptions: { password: true },
default: '',
required: true,
},
];
}
```
--------------------------------------------------------------------------------
/src/civitai-records/src/prompts/civitaiMediaEngagement.ts:
--------------------------------------------------------------------------------
```typescript
import * as fs from "fs";
import * as path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const civitaiMediaEngagementPrompt = {
name: "civitai_media_engagement",
description: "Guide for finding and analyzing Civitai media (videos and images) engagement metrics using find_asset, list_civitai_posts, and fetch_civitai_post_assets",
load: async () => {
const content = await fs.promises.readFile(
path.join(__dirname, "civitai-media-engagement.md"),
"utf-8"
);
return content;
},
};
```
--------------------------------------------------------------------------------
/src/imagekit/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/imagekit",
"version": "1.0.2",
"description": "FeedMob MCP server for ImageKit uploads, cropping, and watermarking.",
"main": "dist/server.js",
"bin": "dist/server.js",
"scripts": {
"build": "tsc",
"dev": "tsx src/server.ts",
"start": "node dist/server.js",
"inspect": "fastmcp inspect src/server.ts",
"dev:cli": "fastmcp dev src/server.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "FeedMob",
"license": "MIT",
"type": "module",
"dependencies": {
"@imagekit/nodejs": "^7.1.0",
"@modelcontextprotocol/sdk": "^1.19.1",
"dotenv": "^17.2.3",
"fastmcp": "^3.19.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/node": "^24.6.2",
"tsx": "^4.20.6",
"typescript": "^5.9.3"
}
}
```
--------------------------------------------------------------------------------
/src/liftoff-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/liftoff-reporting",
"version": "0.0.3",
"description": "MCP Server for Liftoff Reporting API",
"main": "dist/index.js",
"type": "module",
"bin": {
"liftoff-reporting": "dist/index.js"
},
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"appsamurai"
],
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20.14.9",
"typescript": "^5.5.2",
"shx": "^0.3.4"
}
}
```
--------------------------------------------------------------------------------
/src/kayzen-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/kayzen-reporting",
"version": "0.1.0",
"description": "MCP server for Kayzen API",
"main": "dist/index.js",
"author": "FeedMob",
"license": "MIT",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"kayzen-reporting": "dist/index.js"
},
"files": [
"dist"
],
"keywords": [
"mcp",
"kayzen",
"reporting"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"axios": "^1.8.3",
"dotenv": "^16.4.7",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^20.17.24",
"ts-node": "^10.9.2",
"typescript": "^5.8.2",
"shx": "^0.3.4"
}
}
```
--------------------------------------------------------------------------------
/src/appsamurai-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/appsamurai-reporting",
"version": "0.0.5",
"description": "MCP Server for AppSamurai Campaign Spend API",
"main": "dist/index.js",
"type": "module",
"bin": {
"appsamurai-reporting": "dist/index.js"
},
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"appsamurai"
],
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20.14.9",
"typescript": "^5.5.2",
"shx": "^0.3.4"
}
}
```
--------------------------------------------------------------------------------
/src/work-journals/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/work-journals",
"version": "0.0.1",
"description": "This is an MCP server for querying and managing work journals.",
"main": "src/index.ts",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"work-journals": "dist/index.js"
},
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"keywords": [
"mcp",
"work-journals"
],
"files": [
"dist",
"README.md"
],
"author": "FeedMob",
"license": "ISC",
"dependencies": {
"fastmcp": "*",
"zod": "^3.23.8",
"date-fns": "^4.1.0"
},
"devDependencies": {
"@types/node": "^24.0.10",
"shx": "^0.3.4",
"typescript": "^5.5.3"
},
"engines": {
"node": ">=16.0.0"
}
}
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/08_migration_add_on_behalf_of.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 08_migration_add_on_behalf_of.sql
-- Add on_behalf_of column to existing production tables
-- Note: Fresh installs already have this column in scripts 04, 05, 06
-- =========================================================
SET ROLE civitai_owner;
-- Add on_behalf_of column to all tables (nullable, will be populated by trigger)
ALTER TABLE civitai.prompts ADD COLUMN on_behalf_of TEXT NULL;
ALTER TABLE civitai.assets ADD COLUMN on_behalf_of TEXT NULL;
ALTER TABLE civitai.civitai_posts ADD COLUMN on_behalf_of TEXT NULL;
-- Create indexes for efficient filtering by on_behalf_of
CREATE INDEX prompts_on_behalf_of_idx ON civitai.prompts(on_behalf_of);
CREATE INDEX assets_on_behalf_of_idx ON civitai.assets(on_behalf_of);
CREATE INDEX civitai_posts_on_behalf_of_idx ON civitai.civitai_posts(on_behalf_of);
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/github-issues/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/github-issues",
"version": "0.0.5",
"description": "MCP server for using the GitHub API",
"license": "MIT",
"author": "FeedMob",
"homepage": "https://github.com/feedmob/fm-mcp-servers",
"bugs": "https://github.com/feedmob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"mcp-server-github": "dist/index.js"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"dependencies": {
"date-fns": "^4.1.0",
"fastmcp": "2.1.0",
"@modelcontextprotocol/sdk": "1.0.1",
"@types/node": "^22",
"@types/node-fetch": "^2.6.12",
"node-fetch": "^3.3.2",
"universal-user-agent": "^7.0.2",
"zod": "^3.22.4",
"zod-to-json-schema": "^3.23.5"
},
"devDependencies": {
"shx": "^0.3.4",
"typescript": "^5.6.2"
}
}
```
--------------------------------------------------------------------------------
/src/civitai-records/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/civitai-records",
"version": "0.1.14",
"description": "FeedMob FastMCP server scaffold for Civitai record utilities.",
"main": "dist/server.js",
"bin": "dist/server.js",
"scripts": {
"postinstall": "prisma generate",
"build": "./build.sh",
"dev": "tsx src/server.ts",
"start": "node dist/server.js",
"inspect": "fastmcp inspect src/server.ts",
"dev:cli": "fastmcp dev src/server.ts",
"test": "tsx --test"
},
"keywords": [],
"author": "FeedMob",
"license": "MIT",
"type": "module",
"files": [
"dist/",
"prisma/"
],
"dependencies": {
"@modelcontextprotocol/sdk": "^1.19.1",
"@prisma/client": "^6.18.0",
"dotenv": "^17.2.3",
"fastmcp": "^3.25.4",
"prisma": "^6.18.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/node": "^24.6.2",
"tsx": "^4.20.6",
"typescript": "^5.9.3"
}
}
```
--------------------------------------------------------------------------------
/src/inmobi-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/inmobi-reporting",
"version": "0.0.1",
"description": "MCP server for Inmobi API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"inmobi-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"inmobi"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"ts-node": "^10.9.2",
"shx": "^0.3.4",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/smadex-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/smadex-reporting",
"version": "0.0.1",
"description": "MCP server for Smadex API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"smadex-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"smadex"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.15.21",
"@types/node-fetch": "^2.6.12",
"shx": "^0.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/tapjoy-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/tapjoy-reporting",
"version": "0.0.5",
"description": "MCP server for Tapjoy API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"tapjoy-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"tapjoy"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"ts-node": "^10.9.2",
"shx": "^0.3.4",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/feedmob-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/feedmob-reporting",
"version": "0.0.7",
"description": "MCP Server for FeedMob Spend API",
"main": "dist/index.js",
"type": "module",
"bin": {
"feedmob-reporting": "dist/index.js"
},
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"feedmob",
"reporting"
],
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"axios": "^1.7.2",
"dotenv": "^16.4.5",
"jsonwebtoken": "^9.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.0",
"@types/node": "^20.14.9",
"typescript": "^5.5.2",
"shx": "^0.3.4"
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/publish-imagekit.yml:
--------------------------------------------------------------------------------
```yaml
name: Publish ImageKit Package
on:
push:
branches:
- main
paths:
- 'src/imagekit/**'
workflow_dispatch:
inputs:
publish:
description: 'Confirm publishing ImageKit package'
required: true
type: boolean
default: false
jobs:
publish:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || github.event.inputs.publish == 'true' }}
defaults:
run:
working-directory: src/imagekit
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }}
```
--------------------------------------------------------------------------------
/src/applovin-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/applovin-reporting",
"version": "0.0.1",
"description": "MCP server for Applovin API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"applovin-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"applovin"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"ts-node": "^10.9.2",
"shx": "^0.3.4",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/ironsource-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/ironsource-reporting",
"version": "0.0.1",
"description": "MCP server for IronSource API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"ironsource-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"ironsource"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"ts-node": "^10.9.2",
"shx": "^0.3.4",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/mintegral-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/mintegral-reporting",
"version": "0.0.4",
"description": "MCP server for Mintegral Reporting API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"mintegral-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"mintegral"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.11.4",
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"shx": "^0.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/user-activity-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@anthropic/user-activity-reporting",
"version": "0.0.1",
"description": "MCP server for querying user-client relationships and activity data",
"type": "module",
"main": "dist/index.js",
"bin": {
"user-activity-reporting": "dist/index.js"
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/你的用户名/仓库名.git"
},
"keywords": [
"mcp",
"model-context-protocol",
"user-activity",
"reporting"
],
"license": "MIT",
"scripts": {
"dev": "tsx src/index.ts",
"inspect": "npx fastmcp inspect dist/index.js",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.24.2",
"jsonwebtoken": "^9.0.2",
"dotenv": "^16.4.5"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.9",
"@types/node": "^22.13.10",
"tsx": "^4.19.3",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/publish-github-issues.yml:
--------------------------------------------------------------------------------
```yaml
name: Publish GitHub Issues Package
on:
push:
branches:
- main
paths:
- 'src/github-issues/**'
workflow_dispatch:
inputs:
publish:
description: 'Confirm publishing GitHub Issues package'
required: true
type: boolean
default: false
jobs:
publish:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || github.event.inputs.publish == 'true' }}
defaults:
run:
working-directory: src/github-issues
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }}
```
--------------------------------------------------------------------------------
/.github/workflows/publish-work-journals.yml:
--------------------------------------------------------------------------------
```yaml
name: Publish Work Journals Package
on:
push:
branches:
- main
paths:
- 'src/work-journals/**'
workflow_dispatch:
inputs:
publish:
description: 'Confirm publishing Work Journals package'
required: true
type: boolean
default: false
jobs:
publish:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || github.event.inputs.publish == 'true' }}
defaults:
run:
working-directory: src/work-journals
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }}
```
--------------------------------------------------------------------------------
/src/jampp-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/jampp-reporting",
"version": "0.1.3",
"description": "MCP server for Jampp API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"jampp-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"jampp",
"reporting",
"feedmob"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.6.1",
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"ts-node": "^10.9.2",
"shx": "^0.3.4",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/publish-civitai-records.yml:
--------------------------------------------------------------------------------
```yaml
name: Publish Civitai Records Package
on:
push:
branches:
- main
paths:
- 'src/civitai-records/**'
workflow_dispatch:
inputs:
publish:
description: 'Confirm publishing Civitai Records package'
required: true
type: boolean
default: false
jobs:
publish:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || github.event.inputs.publish == 'true' }}
defaults:
run:
working-directory: src/civitai-records
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }}
```
--------------------------------------------------------------------------------
/src/ironsource-aura-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/ironsource-aura-reporting",
"version": "0.0.1",
"description": "MCP server for IronSource Aura API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"ironsource-aura-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"ironsourceAura"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"ts-node": "^10.9.2",
"shx": "^0.3.4",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/femini-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/femini-reporting",
"version": "0.0.5",
"description": "This is a customized MCP server for the Feedmob project, specifically for querying and analyzing ad spend data.",
"main": "src/index.ts",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"femini-reporting": "dist/index.js"
},
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"keywords": [
"mcp",
"femini"
],
"files": [
"dist",
"README.md"
],
"author": "FeedMob",
"license": "ISC",
"dependencies": {
"dotenv": "^16.4.5",
"fastmcp": "^3.6.2",
"jsonwebtoken": "^9.0.2",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^24.0.10",
"shx": "^0.3.4",
"typescript": "^5.5.3"
},
"engines": {
"node": ">=16.0.0"
}
}
```
--------------------------------------------------------------------------------
/src/samsung-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/samsung-reporting",
"version": "0.1.2",
"description": "MCP server for Samsung API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"samsung-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"samsung"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"jsonwebtoken": "^9.0.2",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.15.21",
"@types/node-fetch": "^2.6.12",
"shx": "^0.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/singular-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/singular-reporting",
"version": "0.0.3",
"description": "MCP server for Singular Reporting",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"singular-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"singular",
"reporting",
"feedmob"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"@types/node": "^20.11.24",
"axios": "^1.6.7",
"dotenv": "^16.4.5",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"ts-node": "^10.9.2",
"shx": "^0.3.4",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/rtb-house-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/rtb-house-reporting",
"version": "0.0.2",
"description": "MCP server for RTB House API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"rtb-house-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"rtb-house"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"jsonwebtoken": "^9.0.2",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.15.21",
"@types/node-fetch": "^2.6.12",
"shx": "^0.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/sensor-tower-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/sensor-tower-reporting",
"version": "0.1.2",
"description": "MCP server for sensor-tower API",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"sensor-tower-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"sensor-tower-reporting"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"dotenv": "^16.4.7",
"jsonwebtoken": "^9.0.2",
"node-fetch": "^2.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.7",
"@types/node": "^22.15.21",
"@types/node-fetch": "^2.6.12",
"shx": "^0.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/.github/workflows/publish-n8n-nodes-feedmob-direct-spend-visualizer.yml:
--------------------------------------------------------------------------------
```yaml
name: Publish n8n Direct Spend Visualizer Package
on:
push:
branches:
- main
paths:
- 'src/n8n-nodes-feedmob-direct-spend-visualizer/**'
workflow_dispatch:
inputs:
publish:
description: 'Confirm publishing n8n Direct Spend Visualizer package'
required: true
type: boolean
default: false
jobs:
publish:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' || github.event.inputs.publish == 'true' }}
defaults:
run:
working-directory: src/n8n-nodes-feedmob-direct-spend-visualizer
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish to npm
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }}
```
--------------------------------------------------------------------------------
/src/civitai-records/src/tools/getWorkflowGuide.ts:
--------------------------------------------------------------------------------
```typescript
import type { ContentResult } from "fastmcp";
import * as fs from "fs";
import * as path from "path";
import { fileURLToPath } from "url";
import { z } from "zod";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const getWorkflowGuideParameters = z.object({});
export type GetWorkflowGuideParameters = z.infer<typeof getWorkflowGuideParameters>;
export const getWorkflowGuideTool = {
name: "get_workflow_guide",
description: "Get the complete step-by-step guide for properly recording Civitai posts, prompts, and assets. Use this to understand the correct workflow order and best practices.",
parameters: getWorkflowGuideParameters,
execute: async (): Promise<ContentResult> => {
const workflowContent = await fs.promises.readFile(
path.join(__dirname, "../prompts/record-civitai-workflow.md"),
"utf-8"
);
return {
content: [
{
type: "text",
text: workflowContent,
},
],
} satisfies ContentResult;
},
};
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/09_migration_update_functions_for_on_behalf_of.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 09_update_functions_for_on_behalf_of.sql
-- Update trigger functions to support on_behalf_of column
-- =========================================================
SET ROLE civitai_owner;
-- Update BEFORE trigger: maintain created_by/updated_by/on_behalf_of + timestamps
CREATE OR REPLACE FUNCTION civitai.set_created_updated_by()
RETURNS trigger AS $$
BEGIN
IF (TG_OP = 'INSERT') THEN
NEW.created_by := current_user; -- actual login role
NEW.updated_by := current_user;
-- Auto-populate on_behalf_of from created_by if not explicitly set
IF NEW.on_behalf_of IS NULL THEN
NEW.on_behalf_of := NEW.created_by;
END IF;
NEW.created_at := COALESCE(NEW.created_at, now());
NEW.updated_at := now();
ELSIF (TG_OP = 'UPDATE') THEN
IF NEW.created_by IS DISTINCT FROM OLD.created_by THEN
RAISE EXCEPTION 'created_by is immutable';
END IF;
NEW.updated_by := current_user;
NEW.updated_at := now();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/04_create_prompts.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 04_create_prompts.sql
-- Define civitai.prompts and register it for grants/triggers/RLS
-- =========================================================
SET ROLE civitai_owner;
CREATE TABLE civitai.prompts (
id bigserial PRIMARY KEY,
content text NOT NULL,
llm_model_provider text NULL,
llm_model text NULL,
purpose text NULL,
metadata jsonb,
created_by text NOT NULL DEFAULT current_user,
updated_by text NOT NULL DEFAULT current_user,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- Permissive mode: any user can update any record
SELECT civitai.register_audited_table(
p_table := 'civitai.prompts'::regclass,
p_grant_role := 'civitai_user',
p_id_col := 'id',
p_rls_mode := 'permissive', -- switch to 'owned' for creator-only updates
p_block_delete := true,
p_protect_audit_cols := true
);
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/n8n-nodes-sensor-tower/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/n8n-nodes-sensor-tower",
"version": "0.1.0",
"description": "n8n nodes for Sensor Tower Reporting (wraps MCP tools)",
"keywords": [
"n8n-community-node-package",
"n8n",
"sensor-tower",
"mcp"
],
"author": "FeedMob",
"license": "MIT",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"main": "dist/index.js",
"scripts": {
"build": "tsc && node -e \"fs=require('fs');fs.mkdirSync('dist/nodes/SensorTower',{recursive:true});fs.copyFileSync('nodes/SensorTower/logo.svg','dist/nodes/SensorTower/logo.svg')\"",
"dev": "tsc --watch",
"prepare": "npm run build"
},
"n8n": {
"nodes": [
"./dist/nodes/SensorTower/SensorTower.node.js"
],
"credentials": [
"./dist/credentials/SensorTowerApi.credentials.js"
]
},
"publishConfig": {
"access": "public"
},
"dependencies": {},
"devDependencies": {
"n8n-core": "^1.68.0",
"n8n-workflow": "^1.68.0",
"typescript": "^5.8.2"
},
"engines": {
"node": ">=18.17.0"
},
"files": [
"dist"
]
}
```
--------------------------------------------------------------------------------
/src/impact-radius-reporting/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/impact-radius-reporting",
"version": "0.0.1",
"description": "MCP server for Impact Radius Reporting",
"main": "dist/index.js",
"author": "FeedMob",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"type": "module",
"bin": {
"impact-radius-reporting": "dist/index.js"
},
"files": [
"dist",
"README.md"
],
"keywords": [
"mcp",
"impact-radius",
"reporting",
"feedmob"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch",
"test": "node test_fetch_action_list.js"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.7.0",
"@types/node": "^20.11.24",
"axios": "^1.6.7",
"dotenv": "^16.4.5",
"jsonwebtoken": "^9.0.2",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.0",
"@types/node": "^22.13.10",
"@types/node-fetch": "^2.6.12",
"shx": "^0.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.8.2"
}
}
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/scripts/setup-plugin.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
const { spawnSync } = require('child_process');
const { existsSync, mkdirSync } = require('fs');
const { join } = require('path');
const REPO_URL = 'https://github.com/feed-mob/claude-code-marketplace.git';
const VENDOR_DIR = join(__dirname, '..', 'vendor');
const TARGET_DIR = join(VENDOR_DIR, 'claude-code-marketplace');
function run(command, args, options = {}) {
const result = spawnSync(command, args, { stdio: 'inherit', ...options });
if (result.status !== 0) {
throw new Error(`Command ${command} ${args.join(' ')} failed with code ${result.status ?? 'unknown'}`);
}
}
function ensureRepo() {
if (!existsSync(VENDOR_DIR)) {
mkdirSync(VENDOR_DIR, { recursive: true });
}
if (!existsSync(TARGET_DIR)) {
run('git', ['clone', '--depth', '1', REPO_URL, TARGET_DIR]);
return;
}
run('git', ['-C', TARGET_DIR, 'fetch', '--all', '--prune']);
run('git', ['-C', TARGET_DIR, 'reset', '--hard', 'origin/main']);
}
try {
ensureRepo();
console.log(`[setup-plugin] Ready: ${TARGET_DIR}`);
} catch (error) {
console.error('[setup-plugin] Failed to prepare plugin repository:', error.message);
process.exit(1);
}
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/12_fix_trigger_for_tables_without_on_behalf_of.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 12_fix_trigger_for_tables_without_on_behalf_of.sql
-- Update trigger function to conditionally handle on_behalf_of
-- =========================================================
SET ROLE civitai_owner;
CREATE OR REPLACE FUNCTION civitai.set_created_updated_by()
RETURNS trigger AS $$
BEGIN
IF (TG_OP = 'INSERT') THEN
NEW.created_by := current_user;
NEW.updated_by := current_user;
-- Auto-populate on_behalf_of from created_by if not explicitly set
-- Silently skip if the column doesn't exist
BEGIN
IF NEW.on_behalf_of IS NULL THEN
NEW.on_behalf_of := NEW.created_by;
END IF;
EXCEPTION
WHEN undefined_column THEN
-- Table doesn't have on_behalf_of column, skip
NULL;
END;
NEW.created_at := COALESCE(NEW.created_at, now());
NEW.updated_at := now();
ELSIF (TG_OP = 'UPDATE') THEN
IF NEW.created_by IS DISTINCT FROM OLD.created_by THEN
RAISE EXCEPTION 'created_by is immutable';
END IF;
NEW.updated_by := current_user;
NEW.updated_at := now();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/06_create_civitai_posts.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 06_create_civitai_posts.sql
-- Define civitai.civitai_posts and register it for grants/triggers/RLS
-- =========================================================
SET ROLE civitai_owner;
CREATE TYPE civitai.post_status AS ENUM ('pending', 'published', 'failed');
CREATE TABLE civitai.civitai_posts (
id bigserial PRIMARY KEY,
title text NULL,
description text NULL,
civitai_id text NOT NULL,
civitai_url text NOT NULL,
civitai_account text NOT NULL DEFAULT 'c29',
status civitai.post_status NOT NULL,
metadata jsonb,
created_by text NOT NULL DEFAULT current_user,
updated_by text NOT NULL DEFAULT current_user,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- Permissive mode: any user can update any record
SELECT civitai.register_audited_table(
p_table := 'civitai.civitai_posts'::regclass,
p_grant_role := 'civitai_user',
p_id_col := 'id',
p_rls_mode := 'permissive',
p_block_delete := true,
p_protect_audit_cols := true
);
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/11_create_asset_stats.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 11_create_asset_stats.sql
-- Define civitai.asset_stats and register it for grants/triggers/RLS
-- =========================================================
SET ROLE civitai_owner;
CREATE TABLE civitai.asset_stats (
id bigserial PRIMARY KEY,
asset_id bigint NOT NULL REFERENCES civitai.assets(id),
cry_count bigint NOT NULL DEFAULT 0,
laugh_count bigint NOT NULL DEFAULT 0,
like_count bigint NOT NULL DEFAULT 0,
dislike_count bigint NOT NULL DEFAULT 0,
heart_count bigint NOT NULL DEFAULT 0,
comment_count bigint NOT NULL DEFAULT 0,
created_by text NOT NULL DEFAULT current_user,
updated_by text NOT NULL DEFAULT current_user,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE(asset_id)
);
-- Permissive mode: any user can update any record
SELECT civitai.register_audited_table(
p_table := 'civitai.asset_stats'::regclass,
p_grant_role := 'civitai_user',
p_id_col := 'id',
p_rls_mode := 'permissive',
p_block_delete := true,
p_protect_audit_cols := true
);
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/scripts/build-assets.js:
--------------------------------------------------------------------------------
```javascript
#!/usr/bin/env node
const { cpSync, existsSync, mkdirSync } = require('fs');
const { dirname, join } = require('path');
const ROOT_DIR = join(__dirname, '..');
const DIST_DIR = join(ROOT_DIR, 'dist');
const LOGO_SRC = join(ROOT_DIR, 'nodes', 'FeedmobDirectSpendVisualizer', 'logo.svg');
const LOGO_DEST = join(DIST_DIR, 'nodes', 'FeedmobDirectSpendVisualizer', 'logo.svg');
const VENDOR_SRC = join(ROOT_DIR, 'vendor', 'claude-code-marketplace');
const VENDOR_PLUGIN_SRC = join(VENDOR_SRC, 'plugins', 'direct-spend-visualizer');
const VENDOR_PLUGIN_DEST = join(DIST_DIR, 'vendor', 'claude-code-marketplace', 'plugins', 'direct-spend-visualizer');
function ensureDir(path) {
if (!existsSync(path)) {
mkdirSync(path, { recursive: true });
}
}
function copyLogo() {
ensureDir(dirname(LOGO_DEST));
cpSync(LOGO_SRC, LOGO_DEST);
}
function copyPlugin() {
if (!existsSync(VENDOR_PLUGIN_SRC)) {
throw new Error(`Plugin source directory not found at ${VENDOR_PLUGIN_SRC}. Run npm install to clone the marketplace repo.`);
}
ensureDir(dirname(VENDOR_PLUGIN_DEST));
cpSync(VENDOR_PLUGIN_SRC, VENDOR_PLUGIN_DEST, { recursive: true });
}
function main() {
copyLogo();
copyPlugin();
console.log('[build-assets] Copied assets to dist');
}
main();
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "@feedmob/n8n-nodes-feedmob-direct-spend-visualizer",
"version": "0.1.3",
"description": "n8n nodes for orchestrating FeedMob direct spend visualizations through Claude Agent SDK plugins",
"keywords": [
"n8n-community-node-package",
"n8n",
"feedmob",
"direct-spend",
"claude-agent-sdk"
],
"author": "FeedMob",
"license": "MIT",
"homepage": "https://github.com/feed-mob/fm-mcp-servers",
"bugs": "https://github.com/feed-mob/fm-mcp-servers/issues",
"main": "dist/index.js",
"scripts": {
"postinstall": "node scripts/setup-plugin.js",
"build": "tsc && node scripts/build-assets.js",
"dev": "tsc --watch",
"prepare": "npm run build"
},
"n8n": {
"nodes": [
"./dist/nodes/FeedmobDirectSpendVisualizer/FeedmobDirectSpendVisualizer.node.js"
],
"credentials": [
"./dist/credentials/FeedmobDirectSpendVisualizerApi.credentials.js"
]
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.49"
},
"devDependencies": {
"@types/node": "^20.16.5",
"n8n-core": "^1.68.0",
"n8n-workflow": "^1.68.0",
"typescript": "^5.8.2"
},
"engines": {
"node": ">=18.17.0"
},
"files": [
"dist",
"scripts/setup-plugin.js",
"scripts/build-assets.js"
]
}
```
--------------------------------------------------------------------------------
/src/civitai-records/src/tools/getMediaEngagementGuide.ts:
--------------------------------------------------------------------------------
```typescript
import type { ContentResult } from "fastmcp";
import { z } from "zod";
import * as fs from "fs";
import * as path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const getMediaEngagementGuideParameters = z.object({});
export type GetMediaEngagementGuideParameters = z.infer<
typeof getMediaEngagementGuideParameters
>;
export const getMediaEngagementGuideTool = {
name: "get_media_engagement_guide",
description:
"Get the comprehensive guide for finding and analyzing Civitai media (videos and images) engagement metrics. This guide explains how to use find_asset, list_civitai_posts, and fetch_civitai_post_assets to retrieve engagement data like likes, hearts, comments, and other reactions for videos and images on Civitai.",
parameters: getMediaEngagementGuideParameters,
execute: async (
_params: GetMediaEngagementGuideParameters
): Promise<ContentResult> => {
const guidePath = path.join(
__dirname,
"..",
"prompts",
"civitai-media-engagement.md"
);
const content = await fs.promises.readFile(guidePath, "utf-8");
return {
content: [
{
type: "text",
text: content,
},
],
} satisfies ContentResult;
},
};
```
--------------------------------------------------------------------------------
/src/n8n-nodes-feedmob-direct-spend-visualizer/types.d.ts:
--------------------------------------------------------------------------------
```typescript
declare module 'n8n-workflow' {
export interface INodeProperties {
displayName: string;
name: string;
type: string;
default: unknown;
required?: boolean;
description?: string;
placeholder?: string;
typeOptions?: Record<string, unknown>;
displayOptions?: Record<string, unknown>;
options?: Array<{ name: string; value: string; description?: string; action?: string }>;
}
export interface INodeTypeDescription {
displayName: string;
name: string;
icon?: string;
group: string[];
version: number;
subtitle?: string;
description?: string;
defaults: { name: string };
inputs: string[];
outputs: string[];
credentials?: Array<{ name: string; required: boolean }>;
properties: INodeProperties[];
}
export interface INodeExecutionData { json: any }
export interface IExecuteFunctions {
getInputData(): INodeExecutionData[];
getNodeParameter(name: string, itemIndex: number, fallback?: unknown): unknown;
getCredentials(name: string): Promise<Record<string, unknown>>;
}
export interface INodeType {
description: INodeTypeDescription;
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
}
export interface ICredentialType {
name: string;
displayName: string;
properties: INodeProperties[];
}
}
```
--------------------------------------------------------------------------------
/src/n8n-nodes-sensor-tower/types.d.ts:
--------------------------------------------------------------------------------
```typescript
declare module 'n8n-workflow' {
export interface INodeProperties {
displayName: string;
name: string;
type: string;
default: unknown;
required?: boolean;
description?: string;
placeholder?: string;
typeOptions?: Record<string, unknown>;
displayOptions?: Record<string, unknown>;
options?: Array<{ name: string; value: string; description?: string; action?: string }>;
}
export interface INodeTypeDescription {
displayName: string;
name: string;
icon?: string;
group: string[];
version: number;
subtitle?: string;
description?: string;
defaults: { name: string };
inputs: string[];
outputs: string[];
credentials?: Array<{ name: string; required: boolean }>;
properties: INodeProperties[];
}
export interface INodeExecutionData { json: any }
export interface IExecuteFunctions {
getInputData(): INodeExecutionData[];
getNodeParameter(name: string, itemIndex: number, fallback?: unknown): unknown;
getCredentials(name: string): Promise<Record<string, unknown>>;
}
export interface INodeType {
description: INodeTypeDescription;
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
}
export interface ICredentialType {
name: string;
displayName: string;
properties: INodeProperties[];
}
}
```
--------------------------------------------------------------------------------
/src/github-issues/operations/search.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import { githubRequest, buildUrl } from "../common/utils.js";
export const SearchOptions = z.object({
q: z.string(),
order: z.enum(["asc", "desc"]).optional(),
page: z.number().min(1).optional(),
per_page: z.number().min(1).max(100).optional(),
});
export const SearchUsersOptions = SearchOptions.extend({
sort: z.enum(["followers", "repositories", "joined"]).optional(),
});
export const SearchIssuesOptions = SearchOptions.extend({
sort: z.enum([
"comments",
"reactions",
"reactions-+1",
"reactions--1",
"reactions-smile",
"reactions-thinking_face",
"reactions-heart",
"reactions-tada",
"interactions",
"created",
"updated",
]).optional(),
});
export const SearchCodeSchema = SearchOptions;
export const SearchUsersSchema = SearchUsersOptions;
export const SearchIssuesSchema = SearchIssuesOptions;
export async function searchCode(params: z.infer<typeof SearchCodeSchema>) {
return githubRequest(buildUrl("https://api.github.com/search/code", params));
}
export async function searchIssues(params: z.infer<typeof SearchIssuesSchema>) {
return githubRequest(buildUrl("https://api.github.com/search/issues", params));
}
export async function searchUsers(params: z.infer<typeof SearchUsersSchema>) {
return githubRequest(buildUrl("https://api.github.com/search/users", params));
}
```
--------------------------------------------------------------------------------
/src/imagekit/src/services/imageUploader.ts:
--------------------------------------------------------------------------------
```typescript
export interface ImageUploadRequestBase {
/**
* File content to upload. Accepts a base64 string, binary buffer, or remote URL.
*/
file: string;
/** Target filename consumers expect when retrieving the asset. */
fileName: string;
/** Optional folder or path hint for the provider. */
folder?: string;
/** Optional tags to attach to the uploaded asset. */
tags?: string[];
}
export type ImageUploadRequest<TOptions = Record<string, unknown>> =
ImageUploadRequestBase & {
/** Provider-specific options (e.g., privacy flags, overwrite behaviour). */
options?: TOptions;
};
export interface ImageUploadResultBase {
/** Provider-generated identifier for the stored asset. */
id?: string;
/** Resolved URL that callers can use to fetch the asset. */
url?: string;
/** Provider-assigned name for the stored asset. */
name?: string;
/** Arbitrary metadata exposed by the provider. */
metadata?: Record<string, unknown>;
}
export type ImageUploadResult<
TProviderData = Record<string, unknown>,
> = ImageUploadResultBase & {
/** Original provider payload for callers that need full fidelity. */
providerData?: TProviderData;
};
export interface ImageUploader<
Request extends ImageUploadRequest = ImageUploadRequest,
Result extends ImageUploadResult = ImageUploadResult,
> {
upload(request: Request, signal?: AbortSignal): Promise<Result>;
}
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/05_create_assets.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 05_create_assets.sql
-- Define civitai.assets and register it for grants/triggers/RLS
-- =========================================================
SET ROLE civitai_owner;
CREATE TYPE civitai.asset_type AS ENUM ('image', 'video');
CREATE TYPE civitai.asset_source AS ENUM ('generated', 'upload');
CREATE TABLE civitai.assets (
id bigserial PRIMARY KEY,
input_prompt_id bigint REFERENCES civitai.prompts(id) NULL,
output_prompt_id bigint REFERENCES civitai.prompts(id) NULL,
asset_type civitai.asset_type NOT NULL,
asset_source civitai.asset_source NOT NULL,
uri text NOT NULL,
sha256sum text NOT NULL,
civitai_id text,
civitai_url text,
post_id bigint,
metadata jsonb,
created_by text NOT NULL DEFAULT current_user,
updated_by text NOT NULL DEFAULT current_user,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- Permissive mode: any user can update any record
SELECT civitai.register_audited_table(
p_table := 'civitai.assets'::regclass,
p_grant_role := 'civitai_user',
p_id_col := 'id',
p_rls_mode := 'permissive',
p_block_delete := true,
p_protect_audit_cols := true
);
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/imagekit/src/server.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import "dotenv/config";
import { FastMCP } from "fastmcp";
import { z } from "zod";
import {
aspectRatioSchema,
cropAndWatermarkImage,
} from "./tools/cropAndWatermark.js";
import { createUploadFileTool } from "./tools/uploadFile.js";
const server = new FastMCP({
name: "feedmob-imagekit",
version: "1.0.0",
});
const imageKitPrivateKey = process.env.IMAGEKIT_PRIVATE_KEY;
server.addTool({
name: "crop_and_watermark_image",
description:
"Crop an image to a target aspect ratio, optionally add a watermark, and upload to ImageKit when configured.",
parameters: z.object({
imageUrl: z.string().url(),
aspectRatio: aspectRatioSchema,
watermarkText: z
.string()
.trim()
.max(200)
.optional()
.default(""),
}),
execute: async ({ imageUrl, aspectRatio, watermarkText }) => {
const apiKey = process.env.IMAGE_TOOL_API_KEY;
const apiBaseUrl = process.env.IMAGE_TOOL_BASE_URL;
const modelId = process.env.IMAGE_TOOL_MODEL_ID;
if (!apiKey) {
throw new Error("IMAGE_TOOL_API_KEY is not configured");
}
const generatedUrl = await cropAndWatermarkImage({
imageUrl,
aspectRatio,
watermarkText,
apiKey,
apiBaseUrl,
modelId,
imageKit: imageKitPrivateKey
? {
config: { privateKey: imageKitPrivateKey },
}
: undefined,
});
return generatedUrl;
},
});
server.addTool(createUploadFileTool({ imageKitPrivateKey }));
server.start({ transportType: "stdio" });
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/07_add_constraints_and_input_assets.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 07_add_constraints_and_input_assets.sql
-- Add unique constraints and input_asset_ids column
-- =========================================================
SET ROLE civitai_owner;
-- 1. Add unique constraint to civitai_posts.civitai_id to avoid duplications
ALTER TABLE civitai.civitai_posts
ADD CONSTRAINT civitai_posts_civitai_id_unique UNIQUE (civitai_id);
-- 2. Add unique constraints to assets table
-- Note: sha256sum should be unique per asset to prevent duplicate content
ALTER TABLE civitai.assets
ADD CONSTRAINT assets_sha256sum_unique UNIQUE (sha256sum);
-- Note: civitai_id should be unique when not null (partial unique index)
-- This allows multiple NULL values but ensures uniqueness for non-NULL values
CREATE UNIQUE INDEX assets_civitai_id_unique
ON civitai.assets (civitai_id)
WHERE civitai_id IS NOT NULL;
-- 3. Add input_asset_ids column to track input assets used to generate this asset
-- This creates a many-to-many relationship where an asset can reference multiple input assets
ALTER TABLE civitai.assets
ADD COLUMN input_asset_ids bigint[] NOT NULL DEFAULT '{}';
-- Add comment to explain the column
COMMENT ON COLUMN civitai.assets.input_asset_ids IS
'Array of asset IDs that were used as inputs to generate this asset. For example, if this is a video generated from multiple images, those image asset IDs would be listed here.';
-- Create an index to support queries filtering by input_asset_ids
CREATE INDEX assets_input_asset_ids_idx
ON civitai.assets USING GIN (input_asset_ids);
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/civitai-records/infra/db-init/01-roles.sql:
--------------------------------------------------------------------------------
```sql
-- =========================================================
-- 01_roles.sql
-- Roles, schema, base grants, default privileges, events table
-- =========================================================
-- 1) Roles
CREATE ROLE civitai_owner NOLOGIN CREATEROLE;
CREATE ROLE civitai_user NOLOGIN;
GRANT civitai_user TO civitai_owner WITH ADMIN OPTION;
GRANT civitai_owner TO CURRENT_USER; -- membership enables SET ROLE (required for AWS RDS)
-- 2) Schema (owned by civitai_owner)
CREATE SCHEMA civitai AUTHORIZATION civitai_owner;
-- 3) Base schema privileges
REVOKE ALL ON SCHEMA civitai FROM PUBLIC;
GRANT USAGE ON SCHEMA civitai TO civitai_user;
-- 4) Default privileges for future tables created by civitai_owner
ALTER DEFAULT PRIVILEGES FOR ROLE civitai_owner IN SCHEMA civitai
GRANT SELECT, INSERT, UPDATE ON TABLES TO civitai_user;
ALTER DEFAULT PRIVILEGES FOR ROLE civitai_owner IN SCHEMA civitai
REVOKE DELETE ON TABLES FROM civitai_user;
-- 5) Audit events table (append-only; readable by app users)
SET ROLE civitai_owner;
CREATE TABLE civitai.events (
id bigserial PRIMARY KEY,
occurred_at timestamptz NOT NULL DEFAULT now(),
actor text NOT NULL, -- DB user who performed the change
table_name text NOT NULL, -- e.g. 'prompts'
op text NOT NULL, -- 'INSERT' | 'UPDATE'
row_id bigint NOT NULL, -- assumes bigint PK 'id' on target tables
old_data jsonb,
new_data jsonb
);
GRANT SELECT ON civitai.events TO civitai_user;
REVOKE INSERT, UPDATE, DELETE ON civitai.events FROM civitai_user;
RESET ROLE;
```
--------------------------------------------------------------------------------
/src/civitai-records/src/tools/calculateSha256.ts:
--------------------------------------------------------------------------------
```typescript
import type { ContentResult } from "fastmcp";
import { z } from "zod";
import { sha256 } from "../lib/sha256.js";
export const calculateSha256Parameters = z.object({
path: z
.string()
.describe("The file path (local file system path) or URL (http/https) to calculate SHA-256 hash for."),
});
export type CalculateSha256Parameters = z.infer<typeof calculateSha256Parameters>;
export const calculateSha256Tool = {
name: "calculate_sha256",
description: "Calculate SHA-256 hash for a file from a local path or URL.",
parameters: calculateSha256Parameters,
execute: async ({ path }: CalculateSha256Parameters): Promise<ContentResult> => {
try {
const trimmedPath = path.trim();
const sha256sum = await sha256(trimmedPath);
const sourceType = trimmedPath.startsWith("http://") || trimmedPath.startsWith("https://")
? "url"
: "file";
return {
content: [
{
type: "text",
text: JSON.stringify({
success: true,
path: trimmedPath,
source_type: sourceType,
sha256sum,
}, null, 2),
},
],
} satisfies ContentResult;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: JSON.stringify({
success: false,
path: path.trim(),
error: errorMessage,
}, null, 2),
},
],
} satisfies ContentResult;
}
},
};
```
--------------------------------------------------------------------------------
/src/civitai-records/src/server.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import "dotenv/config";
import { FastMCP } from "fastmcp";
import { createPromptTool } from "./tools/createPrompt.js";
import { createAssetTool } from "./tools/createAsset.js";
import { updateAssetTool } from "./tools/updateAsset.js";
import { findAssetTool } from "./tools/findAsset.js";
import { calculateSha256Tool } from "./tools/calculateSha256.js";
import { createCivitaiPostTool } from "./tools/createCivitaiPost.js";
import { listCivitaiPostsTool } from "./tools/listCivitaiPosts.js";
import { getWorkflowGuideTool } from "./tools/getWorkflowGuide.js";
import { fetchCivitaiPostAssetsTool } from "./tools/fetchCivitaiPostAssets.js";
import { getMediaEngagementGuideTool } from "./tools/getMediaEngagementGuide.js";
import { syncPostAssetStatsTool } from "./tools/syncPostAssetStats.js";
import { recordCivitaiWorkflowPrompt } from "./prompts/recordCivitaiWorkflow.js";
import { civitaiMediaEngagementPrompt } from "./prompts/civitaiMediaEngagement.js";
const server = new FastMCP({
name: "feedmob-civitai-records",
version: "0.1.0",
});
server.addPrompt(recordCivitaiWorkflowPrompt);
server.addPrompt(civitaiMediaEngagementPrompt);
server.addTool(getWorkflowGuideTool);
server.addTool(getMediaEngagementGuideTool);
server.addTool(createPromptTool);
server.addTool(createAssetTool);
server.addTool(updateAssetTool);
server.addTool(findAssetTool);
server.addTool(calculateSha256Tool);
server.addTool(createCivitaiPostTool);
server.addTool(listCivitaiPostsTool);
server.addTool(fetchCivitaiPostAssetsTool);
server.addTool(syncPostAssetStatsTool);
server.start({ transportType: "stdio" });
```
--------------------------------------------------------------------------------
/src/civitai-records/src/lib/sha256.ts:
--------------------------------------------------------------------------------
```typescript
import { createReadStream } from 'node:fs';
import { createHash } from 'node:crypto';
import type { BinaryLike } from 'node:crypto';
import { Readable } from 'node:stream';
import type { ReadableStream as WebReadableStream } from 'node:stream/web';
const SUPPORTED_PROTOCOLS = new Set(['http:', 'https:']);
async function digestStream(stream: NodeJS.ReadableStream): Promise<string> {
const hash = createHash('sha256');
for await (const chunk of stream) {
hash.update(chunk as BinaryLike);
}
return hash.digest('hex');
}
export async function sha256FromFile(filePath: string): Promise<string> {
const stream = createReadStream(filePath);
try {
return await digestStream(stream);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to calculate SHA-256 for file "${filePath}": ${message}`);
}
}
export async function sha256FromUrl(input: string): Promise<string> {
type FetchResponse = Awaited<ReturnType<typeof fetch>>;
let response: FetchResponse;
try {
response = await fetch(input);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to fetch "${input}" while calculating SHA-256: ${message}`);
}
if (!response.ok) {
throw new Error(`Failed to download "${input}" (status ${response.status})`);
}
if (!response.body) {
throw new Error(`Response for "${input}" does not contain a readable body.`);
}
const stream = Readable.fromWeb(response.body as unknown as WebReadableStream<Uint8Array>);
try {
return await digestStream(stream);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to calculate SHA-256 for remote resource "${input}": ${message}`);
}
}
export async function sha256(resource: string): Promise<string> {
if (isHttpUrl(resource)) {
return sha256FromUrl(resource);
}
return sha256FromFile(resource);
}
function isHttpUrl(resource: string): boolean {
try {
const { protocol } = new URL(resource);
return SUPPORTED_PROTOCOLS.has(protocol);
} catch (_error) {
return false;
}
}
```
--------------------------------------------------------------------------------
/src/github-issues/common/errors.ts:
--------------------------------------------------------------------------------
```typescript
export class GitHubError extends Error {
constructor(
message: string,
public readonly status: number,
public readonly response: unknown
) {
super(message);
this.name = "GitHubError";
}
}
export class GitHubValidationError extends GitHubError {
constructor(message: string, status: number, response: unknown) {
super(message, status, response);
this.name = "GitHubValidationError";
}
}
export class GitHubResourceNotFoundError extends GitHubError {
constructor(resource: string) {
super(`Resource not found: ${resource}`, 404, { message: `${resource} not found` });
this.name = "GitHubResourceNotFoundError";
}
}
export class GitHubAuthenticationError extends GitHubError {
constructor(message = "Authentication failed") {
super(message, 401, { message });
this.name = "GitHubAuthenticationError";
}
}
export class GitHubPermissionError extends GitHubError {
constructor(message = "Insufficient permissions") {
super(message, 403, { message });
this.name = "GitHubPermissionError";
}
}
export class GitHubRateLimitError extends GitHubError {
constructor(
message = "Rate limit exceeded",
public readonly resetAt: Date
) {
super(message, 429, { message, reset_at: resetAt.toISOString() });
this.name = "GitHubRateLimitError";
}
}
export class GitHubConflictError extends GitHubError {
constructor(message: string) {
super(message, 409, { message });
this.name = "GitHubConflictError";
}
}
export function isGitHubError(error: unknown): error is GitHubError {
return error instanceof GitHubError;
}
export function createGitHubError(status: number, response: any): GitHubError {
switch (status) {
case 401:
return new GitHubAuthenticationError(response?.message);
case 403:
return new GitHubPermissionError(response?.message);
case 404:
return new GitHubResourceNotFoundError(response?.message || "Resource");
case 409:
return new GitHubConflictError(response?.message || "Conflict occurred");
case 422:
return new GitHubValidationError(
response?.message || "Validation failed",
status,
response
);
case 429:
return new GitHubRateLimitError(
response?.message,
new Date(response?.reset_at || Date.now() + 60000)
);
default:
return new GitHubError(
response?.message || "GitHub API error",
status,
response
);
}
}
```
--------------------------------------------------------------------------------
/src/civitai-records/src/lib/__tests__/detectRemoteAssetType.test.ts:
--------------------------------------------------------------------------------
```typescript
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { detectRemoteAssetType } from '../detectRemoteAssetType.js';
describe('detectRemoteAssetType', () => {
it('should detect image from URL extension (fallback)', async () => {
const result = await detectRemoteAssetType(
'https://example.com/photo.jpg',
{ skipRemote: true } // Force fallback to URL-based detection
);
assert.strictEqual(result.assetType, 'image');
assert.strictEqual(result.ext, 'jpg');
assert.strictEqual(result.from, 'extension');
});
it('should detect video from URL extension (fallback)', async () => {
const result = await detectRemoteAssetType(
'https://example.com/video.mp4',
{ skipRemote: true }
);
assert.strictEqual(result.assetType, 'video');
assert.strictEqual(result.ext, 'mp4');
assert.strictEqual(result.from, 'extension');
});
it('should detect image from CDN path pattern (fallback)', async () => {
const result = await detectRemoteAssetType(
'https://cdn.example.com/images/abc123',
{ skipRemote: true }
);
assert.strictEqual(result.assetType, 'image');
assert.strictEqual(result.from, 'extension');
});
it('should detect video from CDN path pattern (fallback)', async () => {
const result = await detectRemoteAssetType(
'https://cdn.example.com/videos/xyz789',
{ skipRemote: true }
);
assert.strictEqual(result.assetType, 'video');
assert.strictEqual(result.from, 'extension');
});
it('should return null for unknown URL (fallback)', async () => {
const result = await detectRemoteAssetType(
'https://example.com/unknown',
{ skipRemote: true }
);
assert.strictEqual(result.assetType, null);
assert.strictEqual(result.from, 'fallback');
});
it('should handle various image extensions', async () => {
const extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
for (const ext of extensions) {
const result = await detectRemoteAssetType(
`https://example.com/file.${ext}`,
{ skipRemote: true }
);
assert.strictEqual(result.assetType, 'image', `Failed for .${ext}`);
assert.strictEqual(result.ext, ext);
}
});
it('should handle various video extensions', async () => {
const extensions = ['mp4', 'mov', 'avi', 'webm', 'mkv'];
for (const ext of extensions) {
const result = await detectRemoteAssetType(
`https://example.com/file.${ext}`,
{ skipRemote: true }
);
assert.strictEqual(result.assetType, 'video', `Failed for .${ext}`);
assert.strictEqual(result.ext, ext);
}
});
});
```
--------------------------------------------------------------------------------
/src/civitai-records/src/tools/createPrompt.ts:
--------------------------------------------------------------------------------
```typescript
import type { ContentResult } from "fastmcp";
import { z } from "zod";
import { prisma } from "../lib/prisma.js";
const metadataSchema = z.record(z.any()).nullable().default(null);
export const createPromptParameters = z.object({
prompt_text: z
.string()
.min(1)
.describe("The actual text content of the prompt. This is the main creative or instructional text that describes what the user wants to generate or accomplish."),
llm_model_provider: z
.string()
.nullable()
.default(null)
.describe("The AI model provider used with this prompt (e.g., 'openai', 'anthropic', 'google'). Leave empty if not applicable or unknown."),
llm_model: z
.string()
.nullable()
.default(null)
.describe("The specific AI model name or identifier (e.g., 'gpt-4', 'claude-3-opus', 'gemini-pro'). Leave empty if not applicable or unknown."),
purpose: z
.string()
.nullable()
.default(null)
.describe("The intended purpose or use case for this prompt (e.g., 'image_generation', 'video_creation', 'text_completion'). Leave empty if not applicable or unknown."),
metadata: metadataSchema.describe("Additional structured data about this prompt in JSON format. Can include tags, categories, version info, or any custom fields relevant to your workflow."),
on_behalf_of: z
.string()
.nullable()
.default(null)
.describe("The user account this action is being performed on behalf of. If not provided, defaults to the authenticated database user and can be changed later via update tools."),
});
export type CreatePromptParameters = z.infer<typeof createPromptParameters>;
export const createPromptTool = {
name: "create_prompt",
description: "Save a user's text prompt to the database. Use this to store prompts that will be used for content generation, video creation, or other AI tasks.",
parameters: createPromptParameters,
execute: async ({ prompt_text, llm_model_provider, llm_model, purpose, metadata, on_behalf_of }: CreatePromptParameters): Promise<ContentResult> => {
const prompt = await prisma.prompts.create({
data: {
content: prompt_text,
llm_model_provider,
llm_model,
purpose,
metadata: metadata ?? undefined,
on_behalf_of: on_behalf_of ?? undefined,
},
});
return {
content: [
{
type: "text",
text: JSON.stringify({
prompt_id: prompt.id.toString(),
prompt_text: prompt.content,
llm_model_provider: prompt.llm_model_provider,
llm_model: prompt.llm_model,
on_behalf_of: prompt.on_behalf_of,
created_at: prompt.created_at.toISOString(),
}, null, 2),
},
],
} satisfies ContentResult;
},
};
```
--------------------------------------------------------------------------------
/src/civitai-records/src/lib/__tests__/sha256.test.ts:
--------------------------------------------------------------------------------
```typescript
import { createHash } from 'node:crypto';
import { mkdtemp, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { ReadableStream } from 'node:stream/web';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { sha256, sha256FromFile, sha256FromUrl } from '../sha256.js';
function expectHashFor(value: string): string {
return createHash('sha256').update(value).digest('hex');
}
test('sha256FromFile hashes local files without reading everything into memory', async () => {
const dir = await mkdtemp(join(tmpdir(), 'sha256-'));
const filePath = join(dir, 'payload.txt');
const body = 'civitai-records-test';
await writeFile(filePath, body, { encoding: 'utf8' });
const result = await sha256FromFile(filePath);
assert.equal(result, expectHashFor(body));
});
test('sha256FromUrl streams response bodies to produce a digest', async () => {
const payload = 'remote-hash-fixture';
const originalFetch = globalThis.fetch;
const url = 'https://example.com/file.bin';
globalThis.fetch = (async (input) => {
assert.equal(String(input), url);
const readable = new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new TextEncoder().encode(payload));
controller.close();
},
});
return {
ok: true,
status: 200,
body: readable,
} as Awaited<ReturnType<typeof fetch>>;
}) as typeof fetch;
try {
const result = await sha256FromUrl(url);
assert.equal(result, expectHashFor(payload));
} finally {
globalThis.fetch = originalFetch;
}
});
test('sha256 chooses URL hashing for http(s) resources and file hashing otherwise', async () => {
const payload = 'dispatch-check';
const originalFetch = globalThis.fetch;
const url = 'https://feedmob.example/resource';
let fetchInvocations = 0;
globalThis.fetch = (async () => {
fetchInvocations += 1;
return {
ok: true,
status: 200,
body: new ReadableStream<Uint8Array>({
start(controller) {
controller.enqueue(new TextEncoder().encode(payload));
controller.close();
},
}),
} as Awaited<ReturnType<typeof fetch>>;
}) as typeof fetch;
const dir = await mkdtemp(join(tmpdir(), 'sha256-dispatch-'));
const filePath = join(dir, 'payload.txt');
await writeFile(filePath, payload, { encoding: 'utf8' });
try {
const remoteResult = await sha256(url);
assert.equal(remoteResult, expectHashFor(payload));
assert.equal(fetchInvocations, 1, 'expected fetch to be used for remote URLs');
const localResult = await sha256(filePath);
assert.equal(localResult, expectHashFor(payload));
assert.equal(fetchInvocations, 1, 'expected fetch not to run for local files');
} finally {
globalThis.fetch = originalFetch;
}
});
```
--------------------------------------------------------------------------------
/src/imagekit/src/tools/uploadFile.ts:
--------------------------------------------------------------------------------
```typescript
import { access, readFile } from "node:fs/promises";
import { resolve } from "node:path";
import type { ContentResult } from "fastmcp";
import { z } from "zod";
import {
ImageKitUploader,
imageKitUploadParametersSchema,
type ImageKitUploadRequest,
type ImageKitUploadResponse,
} from "../services/imageKitUpload.js";
export const uploadFileParametersSchema = imageKitUploadParametersSchema.extend({
provider: z.literal("imagekit").default("imagekit"),
});
export type UploadFileParameters = z.infer<typeof uploadFileParametersSchema>;
export interface UploadFileToolConfig {
imageKitPrivateKey?: string;
}
function isRemoteUrl(input: string): boolean {
return /^https?:\/\//i.test(input);
}
function isDataUrl(input: string): boolean {
return input.startsWith("data:");
}
async function resolveFileInput(file: string): Promise<string> {
if (isRemoteUrl(file) || isDataUrl(file)) {
return file;
}
const resolvedPath = resolve(file);
try {
await access(resolvedPath);
} catch {
return file;
}
try {
const fileBuffer = await readFile(resolvedPath);
return fileBuffer.toString("base64");
} catch (error) {
const reason =
error && typeof error === "object" && "message" in error
? (error as Error).message
: String(error);
throw new Error(`Unable to read local file at ${resolvedPath}: ${reason}`);
}
}
export async function executeUploadFile(
params: UploadFileParameters,
privateKey: string,
): Promise<ContentResult> {
if (params.provider !== "imagekit") {
throw new Error(`Unsupported provider: ${params.provider}`);
}
const uploader = new ImageKitUploader({
privateKey,
});
const { provider, file, ...request } = params;
const resolvedFile = await resolveFileInput(file);
const uploadRequest: ImageKitUploadRequest = {
...request,
file: resolvedFile,
};
const result = await uploader.upload(uploadRequest);
const providerData: ImageKitUploadResponse =
(result.providerData as ImageKitUploadResponse | undefined) ?? {};
const displayName = result.name ?? uploadRequest.fileName;
const summary = {
provider,
id: result.id ?? providerData.fileId,
name: displayName,
url: result.url ?? providerData.url,
thumbnailUrl: providerData.thumbnailUrl,
};
return {
content: [
{
type: "text",
text: JSON.stringify({ summary, providerData }, null, 2),
},
],
} satisfies ContentResult;
}
export function createUploadFileTool(config: UploadFileToolConfig) {
return {
name: "upload_file",
description:
"Upload an asset to the configured media provider. Defaults to ImageKit and accepts base64 content, a local file path, or a remote URL.",
parameters: uploadFileParametersSchema,
execute: async (params: UploadFileParameters): Promise<ContentResult> => {
const privateKey = config.imageKitPrivateKey;
if (!privateKey) {
throw new Error("IMAGEKIT_PRIVATE_KEY is not configured");
}
return executeUploadFile(params, privateKey);
},
};
}
```
--------------------------------------------------------------------------------
/src/civitai-records/src/lib/civitaiApi.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
const civitaiImageStatsSchema = z
.object({
cryCountAllTime: z.number().int().nonnegative().optional(),
laughCountAllTime: z.number().int().nonnegative().optional(),
likeCountAllTime: z.number().int().nonnegative().optional(),
dislikeCountAllTime: z.number().int().nonnegative().optional(),
heartCountAllTime: z.number().int().nonnegative().optional(),
commentCountAllTime: z.number().int().nonnegative().optional(),
})
.partial()
.optional();
const civitaiImageItemSchema = z.object({
id: z.number(),
stats: civitaiImageStatsSchema,
createdAt: z.string().optional(),
user: z.object({ username: z.string().optional() }).optional(),
});
const civitaiTrpcResponseSchema = z.object({
result: z.object({
data: z.object({
json: z.object({
nextCursor: z.union([z.number(), z.string()]).nullable().optional(),
items: z.array(civitaiImageItemSchema),
}),
}),
}),
});
export type CivitaiImageStats = {
civitai_id: string;
cry_count: number;
laugh_count: number;
like_count: number;
dislike_count: number;
heart_count: number;
comment_count: number;
civitai_created_at: string | null;
civitai_account: string | null;
};
export async function fetchCivitaiPostImageStats(
postId: string
): Promise<CivitaiImageStats[]> {
const allStats: CivitaiImageStats[] = [];
let nextCursor: number | string | null | undefined = undefined;
let isFirstPage = true;
while (isFirstPage || nextCursor) {
const input: any = {
json: {
postId: parseInt(postId),
pending: false,
browsingLevel: null,
withMeta: false,
include: [],
excludedTagIds: [],
disablePoi: true,
disableMinor: false,
cursor: nextCursor ?? null,
},
meta: {
values: {
browsingLevel: ["undefined"],
cursor: ["undefined"],
},
},
};
if (nextCursor) {
delete input.meta.values.cursor;
}
const url = new URL("https://civitai.com/api/trpc/image.getInfinite");
url.searchParams.set("input", JSON.stringify(input));
const response = await fetch(url, {
method: "GET",
headers: {
Accept: "application/json",
},
});
if (!response.ok) {
throw new Error(
`Failed to fetch post images from Civitai TRPC (status ${response.status} ${response.statusText})`
);
}
let json: unknown;
try {
json = await response.json();
} catch (error) {
throw new Error("Civitai response was not valid JSON");
}
const parsed = civitaiTrpcResponseSchema.parse(json);
const items = parsed.result.data.json.items;
for (const item of items) {
const stats = item.stats ?? {};
allStats.push({
civitai_id: item.id.toString(),
cry_count: stats.cryCountAllTime ?? 0,
laugh_count: stats.laughCountAllTime ?? 0,
like_count: stats.likeCountAllTime ?? 0,
dislike_count: stats.dislikeCountAllTime ?? 0,
heart_count: stats.heartCountAllTime ?? 0,
comment_count: stats.commentCountAllTime ?? 0,
civitai_created_at: item.createdAt ?? null,
civitai_account: item.user?.username ?? null,
});
}
nextCursor = parsed.result.data.json.nextCursor;
isFirstPage = false;
}
return allStats;
}
```
--------------------------------------------------------------------------------
/src/civitai-records/src/tools/createCivitaiPost.ts:
--------------------------------------------------------------------------------
```typescript
import type { ContentResult } from "fastmcp";
import { z } from "zod";
import { prisma } from "../lib/prisma.js";
import { handleDatabaseError } from "../lib/handleDatabaseError.js";
const metadataSchema = z.record(z.any()).nullable().default(null);
export const createCivitaiPostParameters = z.object({
civitai_id: z
.string()
.min(1)
.describe("The numeric post ID from the Civitai post URL. Extract this from URLs like https://civitai.com/posts/23602354 where the ID is 23602354."),
civitai_url: z
.string()
.min(1)
.describe("The full public URL where this publication can be viewed on Civitai (e.g., 'https://civitai.com/posts/12345678')."),
status: z
.enum(["pending", "published", "failed"])
.default("published")
.describe("The publication status. Use 'published' for successful posts, 'pending' for scheduled posts, or 'failed' for unsuccessful attempts."),
title: z
.string()
.nullable()
.default(null)
.describe("The title of the publication as it appears on Civitai."),
description: z
.string()
.nullable()
.default(null)
.describe("The description or caption of the publication as posted to Civitai."),
metadata: metadataSchema.describe("Additional structured data about this post in JSON format. Can include Civitai API response, engagement metrics (views, likes, comments), tags, categories, workflow details, or any custom fields relevant to tracking this post."),
on_behalf_of: z
.string()
.nullable()
.default(null)
.describe("The user account this action is being performed on behalf of. If not provided, defaults to the authenticated database user and can be modified later if needed."),
});
export type CreateCivitaiPostParameters = z.infer<typeof createCivitaiPostParameters>;
export const createCivitaiPostTool = {
name: "create_civitai_post",
description: "Record a publication to Civitai.com. Use this after publishing content to Civitai to track the publication in the database. Note: To link an asset to this post, update the asset's post_id field using the update_asset tool.",
parameters: createCivitaiPostParameters,
execute: async ({
civitai_id,
civitai_url,
status,
title,
description,
metadata,
on_behalf_of,
}: CreateCivitaiPostParameters): Promise<ContentResult> => {
const accountValue = process.env.CIVITAI_ACCOUNT ?? "c29";
const post = await prisma.civitai_posts.create({
data: {
civitai_id,
civitai_url,
civitai_account: accountValue,
status,
title: title ?? undefined,
description: description ?? undefined,
metadata: metadata ?? undefined,
on_behalf_of: on_behalf_of ?? undefined,
},
}).catch(error => handleDatabaseError(error, `Civitai ID: ${civitai_id}`));
return {
content: [
{
type: "text",
text: JSON.stringify({
post_id: post.id.toString(),
civitai_id: post.civitai_id,
civitai_url: post.civitai_url,
civitai_account: post.civitai_account,
status: post.status,
title: post.title,
description: post.description,
on_behalf_of: post.on_behalf_of,
created_at: post.created_at.toISOString(),
}, null, 2),
},
],
} satisfies ContentResult;
},
};
```
--------------------------------------------------------------------------------
/src/civitai-records/docs/civitai-owner-createrole.md:
--------------------------------------------------------------------------------
```markdown
# 📘 Why We Give `civitai_owner` Limited Admin Powers
## 1️⃣ Context
Our Civitai PostgreSQL deployment provisions human users (e.g. `richard`, `alice`) via SQL during container startup. Each login should:
* Use its own database role and password.
* Only perform `SELECT`, `INSERT`, `UPDATE` on application tables.
* Automatically populate audit columns (`created_by`, `updated_by`).
* Be created idempotently by migration scripts (`infra/db-init/*.sql`).
PostgreSQL enforces that only roles with `CREATEROLE` can create or alter other roles, so we need a safe way for migrations to provision logins without handing blanket superuser access to the runtime roles.
---
## 2️⃣ What the Code Does Today
`infra/db-init/01-roles.sql` establishes two structural roles:
```sql
CREATE ROLE civitai_owner NOLOGIN CREATEROLE;
CREATE ROLE civitai_user NOLOGIN;
GRANT civitai_user TO civitai_owner WITH ADMIN OPTION;
```
`civitai_owner` owns the schema and is marked `CREATEROLE`, which lets it manage downstream login roles. Application connections inherit privileges through `civitai_user`, keeping runtime permissions narrow.
`infra/db-init/02_functions.sql` defines the helper that migrations call:
```sql
CREATE OR REPLACE FUNCTION civitai.create_app_user(p_username text, p_password text)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- validates, creates or alters the login role, then grants civitai_user
END;
$$;
```
Because the function is `SECURITY DEFINER` and owned by `civitai_owner`, the code executes with `civitai_owner`'s `CREATEROLE` privilege. `infra/db-init/03_init.sql` then provisions seed logins using this helper.
---
## 3️⃣ Why This Shape Works
* **Least privilege at runtime** — client connections still use logins that only have `civitai_user`; they cannot create roles or bypass row-level security.
* **Controlled role creation** — only the migration-time helper inherits `CREATEROLE`, isolating the capability inside a single audited function.
* **Idempotent provisioning** — the function updates existing roles (`ALTER ROLE ... PASSWORD`) when rerun, so migrations stay reentrant.
* **Audit consistency** — the helper hands out `civitai_user`, and our triggers (`set_created_updated_by`, `audit_event`) rely on `current_user` to capture who changed a record.
---
## 4️⃣ Security Guardrails in Place
* `civitai_owner` is `NOLOGIN`, so no one can connect as it directly.
* The helper only grants `civitai_user`, preventing privilege escalation even if it is misused.
* Table-level grants, trigger wiring, and row-level policies are enforced through `civitai.register_audited_table`, ensuring new tables inherit the right protections automatically.
> 📌 We intentionally **do not** introduce an extra `civitai_admin` role in this repository; the existing migrations already give `civitai_owner` the minimal elevated capability required to run `create_app_user` during initialization.
---
## 5️⃣ Call Flow Overview
```
postgres (container superuser)
│
├── executes 01-roles.sql → civitai_owner (NOLOGIN, CREATEROLE)
│
├── executes 02_functions.sql → civitai.create_app_user() [SECURITY DEFINER]
│
└── executes 03_init.sql → civitai.create_app_user('richard', '...')
│
▼
civitai_owner (definer)
│
├── CREATE ROLE richard LOGIN ...
└── GRANT civitai_user TO richard
```
---
## 6️⃣ Takeaway
Granting `CREATEROLE` directly to the schema owner keeps the privilege surface small while letting our migrations operate unattended. The helper function encapsulates role provisioning, making it safe to rerun and easy to audit. No additional admin role is needed for the current code path.
```