About Admanagekit MCP Server
Admanagekit MCP Server is an MCP server published by i2hammad in the Finance category: adManageKit is an open-source Android library designed to simplify the management of Google AdMob ads,tROI firebase tracking, in-app billing, and User Messaging Platform (UMP) consent in Android applications.
The repository has 6 stars and 5 forks, with the last commit yesterday. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/i2hammad/AdManageKitThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Admanagekit MCP Server
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository 7 hours ago. Heuristics on the source, not an audit.
- ✓README presentThe repository ships documentation.
- ✓License declaredUsable under a stated license.
- ✓Tests presentA test suite exists in the repository.
- ✓Dependencies pinnedA lockfile pins dependency versions.
- ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
1 check has not been run yet.
Releases
README
AdManageKit
An Android library that takes the boilerplate out of Google AdMob ads, Google Play Billing and UMP consent, with optional multi-network waterfalls (Yandex) and Jetpack Compose support.
Latest version: 4.4.9 · Release notes · Changelog
Requires Kotlin 2.3+ in your app as of 4.4.8. ⚠️ Avoid 4.4.3: its billing regression makes purchases unusable.
MCP Server · Features · Requirements · Installation · Quick Start · Usage · Documentation · Migration Guide
MCP Server
Using an AI coding assistant? The AdManageKit MCP server lets Claude Code, Claude Desktop, Cursor and other MCP clients search these docs and generate integration code for ads, billing and Compose, in Kotlin or Java.
claude mcp add admanagekit -- npx -y admanagekit-mcp-server
Other clients:
{
"mcpServers": {
"admanagekit": {
"command": "npx",
"args": ["-y", "admanagekit-mcp-server"]
}
}
}
It's configured automatically when you work in this repo.
Features
| Area | What you get |
|---|---|
| Ad formats | Banner (all standard sizes, adaptive, collapsible), native (small / medium / large and 38 NativeTemplateView templates), interstitial, rewarded, app open |
| Loading strategies | ON_DEMAND, ONLY_CACHE, HYBRID per ad type; screen-aware native ad caching with LRU and expiry |
| Frequency control | Time- and count-based interstitial gating, ad pools, splash showOrWaitForAd(), app open activity/screen exclusion |
| Reliability | Retry with exponential backoff, main-thread callback delivery |
| Waterfall | AdMob → Yandex (or any provider) fallback per ad type, with no changes to your ad calls |
| Billing | Play Billing 9: subscriptions, one-time products, consumables, offers and paywalls, trial eligibility, account hold recovery |
| Privacy | UMP consent (GDPR/CCPA); ads hidden automatically for purchased users |
| Compose | Composable wrappers for every ad format |
| Analytics | Firebase ad_impression, ad_paid_event and ad_failed_to_load events for tROAS |
| Module | Artifact | Purpose |
|---|---|---|
| Ads | ad-manage-kit |
Ad managers, views, waterfall, UMP |
| Core | ad-manage-kit-core |
Shared interfaces and provider registry |
| Billing | ad-manage-kit-billing |
Google Play Billing wrapper (AppPurchase) |
| Compose | ad-manage-kit-compose |
Jetpack Compose components |
| Yandex | ad-manage-kit-yandex |
Yandex Mobile Ads waterfall provider |
Requirements
minSdk24,compileSdk37+- Kotlin 2.3+ (the library and Firebase BOM 35 ship Kotlin 2.4 metadata)
- An explicit
MobileAds.initialize()call before the first ad request (see Quick Start)
Next-Gen GMA SDK
Since 4.2.0, AdManageKit runs on Google's Next-Gen Google Mobile Ads SDK (com.google.android.libraries.ads.mobile.sdk, 1.5.0 as of 4.4.8) instead of the legacy play-services-ads. Three things differ from the legacy SDK:
- Initialization is explicit. The SDK no longer lazy-inits on first use. AdManageKit doesn't call
MobileAds.initialize()for you, because it doesn't own your consent flow. - Callbacks fire on a background thread. Every AdManageKit manager, provider and view re-posts its callbacks to the main thread, so this is transparent to you.
- Error and value types changed.
AdKitError,AdKitLoadErrorandAdKitValuenow resolve to Next-Gen SDK types. Callback implementations don't need to change, only code that reads legacy-only fields. See Migrating to 4.2.0.
Installation
Add JitPack to settings.gradle:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
Add the modules you need to your app's build.gradle:
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit:v4.4.9'
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-billing:v4.4.9'
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-core:v4.4.9'
// Optional: Jetpack Compose support
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-compose:v4.4.9'
// Optional: Yandex Ads waterfall provider
implementation 'com.github.i2hammad.AdManageKit:ad-manage-kit-yandex:v4.4.9'
Quick Start
Configure the library and initialize the SDK in your Application:
class MyApp : Application() {
lateinit var appOpenManager: AppOpenManager
override fun onCreate() {
super.onCreate()
AdManageKitConfig.apply {
debugMode = BuildConfig.DEBUG
autoRetryFailedAds = true
interstitialLoadingStrategy = AdLoadingStrategy.HYBRID
appOpenLoadingStrategy = AdLoadingStrategy.HYBRID
nativeLoadingStrategy = AdLoadingStrategy.HYBRID
}
// Hide ads for users who bought premium
BillingConfig.setPurchaseProvider(BillingPurchaseProvider())
// initialize() must run off the main thread, and AppOpenManager must be
// created only after it returns (the SDK rejects loads before that).
val config = InitializationConfig.Builder("ca-app-pub-xxx~yyy").build()
Thread {
MobileAds.initialize(this, config)
Handler(Looper.getMainLooper()).post {
appOpenManager = AppOpenManager(this, "ca-app-pub-xxx/yyy")
}
}.start()
}
}
Request consent before loading ads:
AdsConsentManager.getInstance(this).requestUMP(
activity = this,
isDebug = true,
testDeviceId = "TEST_DEVICE_ID",
resetConsent = false,
listener = object : UMPResultListener {
override fun onCheckUMPSuccess(isConsentGiven: Boolean) {
if (isConsentGiven) {
AdManager.getInstance().loadInterstitialAd(activity, adUnitId)
}
}
}
)
The sample app's MyApplication.kt shows the full setup, including test device ids and the Yandex waterfall.
Usage
Banner Ads
<com.i2hammad.admanagekit.admob.BannerAdView
android:id="@+id/bannerAdView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:bannerAdSize="adaptive" />
bannerAdView.loadBanner(this, "ca-app-pub-xxx/yyy")
// Fixed size: BANNER, LARGE_BANNER, MEDIUM_RECTANGLE, FULL_BANNER, LEADERBOARD
bannerAdView.loadBanner(this, "ca-app-pub-xxx/yyy", BannerAdSize.MEDIUM_RECTANGLE)
// Collapsible banner (adaptive size only)
bannerAdView.loadCollapsibleBanner(this, "ca-app-pub-xxx/yyy", true)
ADAPTIVE (the default) is Google's full-width anchored adaptive banner; ADAPTIVE_LARGE opts into the taller large anchored format. See the Banner Ad Guide.
Native Ads
NativeTemplateView renders any of 38 templates from XML or code:
<com.i2hammad.admanagekit.admob.NativeTemplateView
android:id="@+id/nativeTemplateView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:adTemplate="material3" />
nativeTemplateView.setTemplate(NativeAdTemplate.MAGAZINE)
nativeTemplateView.loadNativeAd(activity, adUnitId, object : AdLoadCallback() {
override fun onAdLoaded() { }
override fun onFailedToLoad(error: AdError?) { }
})
// Override the loading strategy for one call
nativeTemplateView.loadNativeAd(activity, adUnitId, callback, AdLoadingStrategy.ONLY_CACHE)
Popular templates: card_modern, material3, minimal, compact_horizontal, list_item, magazine, app_store, social_feed, medium_horizontal, flexible, plus the video_* family. See the NativeTemplateView Guide for all 38.
The fixed-layout views NativeBannerSmall, NativeBannerMedium and NativeLarge work the same way:
nativeBannerSmall.loadNativeBannerAd(activity, adUnitId, useCachedAd = true)
Interstitial Ads
val ads = AdManager.getInstance()
ads.loadInterstitialAd(this, "ca-app-pub-xxx/yyy")
val callback = object : AdManagerCallback() {
override fun onNextAction() { navigateNext() } // fires once on show, skip or failure
}
ads.forceShowInterstitial(this, callback) // show now
ads.forceShowInterstitialWithDialog(this, callback) // with loading dialog
ads.showInterstitialAdByTime(this, callback) // at most every 15s (default)
ads.showInterstitialAdByCount(this, callback, maxDisplayCount = 3)
// Splash screen: show a ready ad, or wait up to the timeout for one
ads.showOrWaitForAd(activity = this, callback = callback, timeoutMillis = 10_000)
Ads reload after showing by default. Turn that off globally with AdManageKitConfig.interstitialAutoReload = false, or per call:
InterstitialAdBuilder.with(activity)
.adUnit(adUnitId)
.autoReload(true)
.show { navigateNext() }
More: Interstitial Ads · InterstitialAdBuilder · Frequency Control
Rewarded Ads
RewardedAdManager.initialize(context, "ca-app-pub-xxx/yyy") // once, e.g. in Application
if (RewardedAdManager.isAdLoaded()) {
RewardedAdManager.showAd(activity, object : RewardedAdManager.RewardedAdCallback {
override fun onRewardEarned(rewardType: String, rewardAmount: Int) {
grantReward(rewardType, rewardAmount)
}
override fun onAdDismissed() { continueGameFlow() }
})
}
RewardedAdManager.loadRewardedAdWithTimeout(context, 5000, callback) // splash screens
More: Rewarded Ads
App Open Ads
appOpenManager.disableAppOpenWithActivity(SplashActivity::class.java) // exclude screens
appOpenManager.forceShowAdIfAvailable(activity, callback)
appOpenManager.skipNextAd()
// Pause around critical flows
appOpenManager.disableAppOpenAdsTemporarily()
appOpenManager.enableAppOpenAds()
Single-activity apps can exclude fragments or navigation destinations instead:
navController.addOnDestinationChangedListener { _, destination, _ ->
appOpenManager.setCurrentScreenTag(destination.label?.toString())
}
appOpenManager.excludeScreenTags("Payment", "Onboarding", "Checkout")
More: App Open Ads
Jetpack Compose
@Composable
fun MyScreen() {
BannerAdCompose(adUnitId = "ca-app-pub-xxx/yyy")
NativeTemplateCompose(
adUnitId = "ca-app-pub-xxx/yyy",
template = NativeAdTemplate.MATERIAL3,
loadingStrategy = AdLoadingStrategy.HYBRID
)
val showInterstitial = rememberInterstitialAd(adUnitId = "ca-app-pub-xxx/yyy", preloadAd = true)
Button(onClick = { showInterstitial() }) { Text("Show Ad") }
// Hidden automatically for purchased users
ConditionalAd {
ProgrammaticNativeBannerMediumCompose(adUnitId = "ca-app-pub-xxx/yyy")
}
}
More: Jetpack Compose Integration
Multi-Provider Waterfall
Add Yandex (or another provider) as a fallback. Existing ad calls pick up the chain automatically:
// In Application.onCreate(), after AdManageKitConfig setup
YandexProviderRegistration.initialize(this)
val admob = AdMobProviderRegistration.create()
val yandex = YandexProviderRegistration.create()
// Map AdMob ad unit ids to their Yandex equivalents
AdUnitMapping.register("ca-app-pub-xxx/your-interstitial", mapOf("yandex" to "R-M-XXXXXX-Y"))
// Order = priority; set a chain per ad type
AdProviderConfig.setInterstitialChain(listOf(admob.interstitialProvider, yandex.interstitialProvider))
AdProviderConfig.setNativeChain(listOf(admob.nativeProvider, yandex.nativeProvider))
More: Multi-Provider Waterfall · Yandex Integration
In-App Purchases
val products = listOf(
PurchaseItem("coins_100", TYPE_IAP.PURCHASE, PurchaseCategory.CONSUMABLE),
PurchaseItem("remove_ads", TYPE_IAP.PURCHASE, PurchaseCategory.REMOVE_ADS),
PurchaseItem("lifetime", TYPE_IAP.PURCHASE, PurchaseCategory.LIFETIME_PREMIUM),
PurchaseItem("premium_monthly", "free_trial", TYPE_IAP.SUBSCRIPTION)
)
val billing = AppPurchase.getInstance()
billing.initBilling(application, products)
billing.purchase(activity, "remove_ads")
billing.subscribe(activity, "premium_monthly")
if (billing.isPurchased()) { /* subscription, lifetime or remove_ads */ }
// Consumables are not auto-consumed: grant, then consume
billing.setPurchaseHistoryListener(object : PurchaseHistoryListener {
override fun onNewPurchase(productId: String, purchase: PurchaseResult) {
if (productId == "coins_100") {
addCoins(100 * purchase.quantity)
billing.consumePurchase(productId)
}
}
override fun onPurchaseConsumed(productId: String, purchase: PurchaseResult) { }
})
Subscription Offers & Paywalls
Play returns only the offers this account is eligible for. Buy the exact offer the user tapped, since subscribe(activity, subsId) picks one for you:
val offers = billing.getOffers("premium_sub")
billing.subscribe(activity, offers[selectedIndex])
billing.getOfferByBasePlanId("premium_sub", "yearly")
billing.getOfferByTag("premium_sub", "popular") // Play Console offer tag
billing.getBestValueOffer("premium_sub") // lowest cost per month
val offer = billing.getBaseOffer("premium_yearly") ?: return
priceLabel.text = offer.basePrice // "$59.99"
perMonthLabel.text = AppPurchase.formatPrice(offer.pricePerMonthMicros, offer.currencyCode)
todayLabel.text = offer.firstCyclePrice // "Free", "$1.99" or "$59.99"
val savings = billing.getSavingsPercent("premium_monthly", "premium_yearly") // "Save 50%"
subscribeButton.text = if (billing.isEligibleForFreeTrial("premium_sub")) "Start free trial" else "Subscribe"
One-time products can carry several offers too (discounts, rentals, pre-orders): see getBestOneTimeOffer() in the In-App Purchase Guide.
Subscription State & Account Hold
when (billing.getSubscriptionState("premium_monthly")) {
SubscriptionState.ACTIVE -> showPremiumUI()
SubscriptionState.CANCELLED -> showRenewalPrompt() // still has access
SubscriptionState.ON_HOLD -> showFixPaymentPrompt() // payment declined, no access
SubscriptionState.EXPIRED -> showSubscribeButton()
}
billing.upgradeSubscription(activity, "premium_yearly")
billing.downgradeSubscription(activity, "premium_basic")
// Let Play walk the user through fixing a declined payment
if (billing.hasSubscriptionOnHold()) {
billing.showInAppMessages(activity, object : InAppMessageListener {
override fun onSubscriptionRecovered(purchaseToken: String) { refreshPremiumUi() }
override fun onNoActionNeeded() { }
})
}
isSubscriptionActive()returnsfalseduring account hold, as Google requires. CheckisSuspended()if you deliberately keep serving those users.
Empty paywall? Register a ProductDetailsListener before initBilling. Its unfetched list names each product id Play declined and why: a misspelled id, an inactive product, or an account that can't see the release track.
More: In-App Purchase Guide · Subscription Offers · Subscription Upgrades
Screenshots
| NativeBannerSmall Ad | Interstitial Ad | App Open Ad | UMP Consent Form |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Documentation
| Topic | Guide |
|---|---|
| Configuration | Configuration Usage · Java Usage |
| Native ads | NativeTemplateView · Native Ads Caching · Preloading |
| Loading strategies | Ad Loading Strategies · Examples |
| Full-screen ads | Interstitial · Rewarded · App Open |
| Compose | Jetpack Compose Integration |
| Waterfall | Multi-Provider Waterfall · Yandex Integration |
| Billing | In-App Purchase Guide · Purchase Categories · Consumables · Subscriptions |
| Reference | API Reference · Dokka API docs (./gradlew dokkaGenerateHtml to build locally) |
| History | Changelog · Release notes |
| AI tools | MCP server for Claude Code, Cursor and other MCP clients |
Migration Guide
Migrating to 4.2.0
The Next-Gen GMA SDK swap (see Next-Gen GMA SDK) is source-compatible for most consumers. It only affects you if your code does one of the following:
1. You read specific members of an error/value object, not just call the callback method.
AdKitError/AdKitLoadError now resolve to the Next-Gen SDK's LoadAdError, which drops the old domain/cause fields and replaces the free-form Int error code with a closed LoadAdError.ErrorCode enum:
// Before (4.1.x and earlier)
override fun onFailedToLoad(error: AdKitError?) {
Log.e(TAG, "Failed: ${error?.code} / ${error?.domain}") // .domain no longer exists
}
// After (4.2.0)
override fun onFailedToLoad(error: AdKitError?) {
Log.e(TAG, "Failed: ${error?.code}") // .code is now a LoadAdError.ErrorCode enum, still fine to log
}
2. You use the raw-callback overload of AdManager.loadInterstitialAd.
// Before
AdManager.getInstance().loadInterstitialAd(context, adUnitId,
object : com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: com.google.android.gms.ads.interstitial.InterstitialAd) { }
override fun onAdFailedToLoad(error: com.google.android.gms.ads.LoadAdError) { }
})
// After
AdManager.getInstance().loadInterstitialAd(context, adUnitId,
object : com.google.android.libraries.ads.mobile.sdk.common.AdLoadCallback<
com.google.android.libraries.ads.mobile.sdk.interstitial.InterstitialAd> {
override fun onAdLoaded(ad: InterstitialAd) { }
override fun onAdFailedToLoad(adError: LoadAdError) { }
})
3. You supply a custom native ad layout XML.
<!-- Before -->
<com.google.android.gms.ads.nativead.NativeAdView ...>
<com.google.android.gms.ads.nativead.MediaView android:id="@+id/ad_media" ... />
<com.google.android.gms.ads.nativead.AdChoicesView android:id="@+id/ad_choices_view" ... />
</com.google.android.gms.ads.nativead.NativeAdView>
<!-- After -->
<com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdView ...>
<com.google.android.libraries.ads.mobile.sdk.nativead.MediaView android:id="@+id/ad_media" ... />
<com.google.android.libraries.ads.mobile.sdk.common.AdChoicesView android:id="@+id/ad_choices_view" ... />
</com.google.android.libraries.ads.mobile.sdk.nativead.NativeAdView>
4. Your Compose callback lambdas' parameter types are explicitly typed.
If you wrote onAdFailedToLoad: (com.google.android.gms.ads.LoadAdError?) -> Unit = { ... } explicitly rather than letting Kotlin infer the type from the composable's signature, update the import to com.google.android.libraries.ads.mobile.sdk.common.LoadAdError.
Everything else:
- Call
MobileAds.initialize()once at app startup if you haven't already — see Next-Gen GMA SDK. AdManageKit doesn't do this for you. - Bump
compileSdkto 37+. - If you relied on
BannerAdView/BannerAdProvider'spause()/resume()actually pausing ad refresh, that's now a no-op — the Next-Gen SDK'sAdViewdoesn't expose it. - Google Play Billing 9.1.0 needs no changes if you only use
AppPurchase/PurchaseItem.
See Release Notes v4.2.0 for the full technical writeup.
Migrating to 3.0.0
Version 3.0.0 is fully backward compatible. Optionally adopt new features:
Smart Splash Ads (Recommended)
Replace separate load + show calls with single showOrWaitForAd():
// Before (v2.9.0) - Two-step approach
AdManager.getInstance().loadInterstitialAdForSplash(this, adUnitId, 10_000, object : AdManagerCallback() {
override fun onNextAction() {
AdManager.getInstance().forceShowInterstitial(this@SplashActivity, callback)
}
})
// After (v3.0.0) - Single smart call
AdManager.getInstance().showOrWaitForAd(
activity = this,
callback = object : AdManagerCallback() {
override fun onNextAction() { navigateNext() }
},
timeoutMillis = 10_000
)
Ad Pool for Higher Show Rate
// Before (single ad unit)
AdManager.getInstance().loadInterstitialAd(context, "single_unit")
// After (multiple ad units for redundancy)
AdManager.getInstance().loadMultipleAdUnits(context, "high_ecpm", "medium_ecpm", "fallback")
App Open Ad Prefetching
// Prefetch before external intents
appOpenManager.prefetchNextAd()
startActivityForResult(cameraIntent, REQUEST_CODE)
Migrating to 2.9.0
Version 2.9.0 has one breaking change for consumable products:
Consumables are no longer auto-consumed. You must manually call consumePurchase():
// Before v2.9.0 (auto-consume)
AppPurchase.getInstance().setConsumePurchase(true) // Deprecated
// After v2.9.0 (manual consume)
AppPurchase.getInstance().setPurchaseHistoryListener(object : PurchaseHistoryListener {
override fun onNewPurchase(productId: String, purchase: PurchaseResult) {
grantItems(productId, purchase.quantity)
AppPurchase.getInstance().consumePurchase(productId) // Manual!
}
override fun onPurchaseConsumed(productId: String, purchase: PurchaseResult) { }
})
Use Purchase Categories for better product classification:
// Before
PurchaseItem("coins", TYPE_IAP.PURCHASE, true) // isConsumable
// After (explicit categories)
PurchaseItem("coins", TYPE_IAP.PURCHASE, PurchaseCategory.CONSUMABLE)
PurchaseItem("remove_ads", TYPE_IAP.PURCHASE, PurchaseCategory.REMOVE_ADS)
Migrating to 2.8.0
Version 2.8.0 is fully backward compatible with one behavioral change:
forceShowInterstitial() now respects loading strategy:
// If you need old behavior (always force fetch), use:
AdManager.getInstance().forceShowInterstitialAlways(activity, callback)
// Or set strategy to ON_DEMAND globally:
AdManageKitConfig.interstitialLoadingStrategy = AdLoadingStrategy.ON_DEMAND
Migrating to 2.7.0
Version 2.7.0 is fully backward compatible. Optionally adopt new features:
// Old way (still works)
val nativeBannerMedium = NativeBannerMedium(context)
nativeBannerMedium.loadNativeBannerAd(activity, adUnitId)
// New unified approach
val nativeTemplateView = NativeTemplateView(context)
nativeTemplateView.setTemplate(NativeAdTemplate.CARD_MODERN)
nativeTemplateView.loadNativeAd(activity, adUnitId)
Sample Project
The app module demonstrates all features. To run:
- Clone:
git clone https://github.com/i2hammad/AdManageKit.git - Open in Android Studio
- Replace placeholder AdMob IDs
- Run on device or emulator
Contributing
- Fork the repository
- Create a branch (
git checkout -b feature/YourFeature) - Commit changes (
git commit -m 'Add YourFeature') - Push (
git push origin feature/YourFeature) - Open a Pull Request
License
Licensed under the MIT License. See LICENSE.
Support
For issues: GitHub Issues or hammadmughal0001@gmail.com
README fetched from the repository 7 hours ago. The original is authoritative.



