# Directory Structure ``` ├── .env.example ├── .gitignore ├── build │ ├── api │ │ ├── getBeerInfo.js │ │ ├── getBeerSearch.js │ │ └── getBreweryInfo.js │ ├── config.js │ ├── constants.js │ ├── index.js │ ├── libs │ │ ├── format.js │ │ └── guards.js │ └── types │ └── untappedApi.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── src │ ├── api │ │ ├── getBeerInfo.ts │ │ ├── getBeerSearch.ts │ │ └── getBreweryInfo.ts │ ├── constants.ts │ ├── index.ts │ ├── libs │ │ ├── format.ts │ │ └── guards.ts │ └── types │ └── untappedApi.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` # Untapped API # Rename this file to .env after you add your values UNTAPPED_API_CLIENT_ID= UNTAPPED_API_CLIENT_SECRET= ``` -------------------------------------------------------------------------------- /.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.* # idea .idea ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # untapped-mcp A Untapped MCP server to be used with claude. ## Setup ### Get API Key ### Usage with Claude Desktop Add the following to your `claude_desktop_config.json`: ```json { "mcpServers": { "Untappd": { "command": "node", "args": ["/Users/user/projects/untapped-mcp/build/index.js"], "env": { "UNTAPPED_API_CLIENT_ID": "<YOUR_CLIENT_ID>", "UNTAPPED_API_CLIENT_SECRET": "<YOUR_CLIENT_SECRET>" } } } } ``` ``` -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- ```typescript // Untapped export const UNTAPPED_API_BASE = "https://api.untappd.com/v4/"; export const UNTAPPED_API_SEARCH = "search/beer"; export const UNTAPPED_API_INFO = "beer/info"; export const UNTAPPED_API_BREWERY_INFO = "/brewery/info/"; ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /src/libs/guards.ts: -------------------------------------------------------------------------------- ```typescript import { UntappdApiErrorResponse } from "../types/untappedApi.js"; export function isUntappdApiError( value: unknown, ): value is UntappdApiErrorResponse { return ( typeof value === "object" && value !== null && "meta" in value && typeof (value as any).meta === "object" && "code" in (value as any).meta && "error_detail" in (value as any).meta && "error_type" in (value as any).meta ); } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "untapped-mcp", "version": "1.0.0", "description": "A Untapped MCP server to be used with claude.", "main": "index.js", "repository": { "type": "git", "url": "git+https://github.com/etoxin/untapped-mcp.git" }, "keywords": [], "author": "Adam Lusted", "license": "ISC", "bugs": { "url": "https://github.com/etoxin/untapped-mcp/issues" }, "homepage": "https://github.com/etoxin/untapped-mcp#readme", "type": "module", "bin": { "untapped": "./build/index.js" }, "files": [ "build" ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", "axios": "^1.8.2", "dotenv": "^16.4.7", "zod": "^3.24.2" }, "devDependencies": { "@types/node": "^22.13.9", "typescript": "^5.8.2" } } ``` -------------------------------------------------------------------------------- /src/api/getBeerInfo.ts: -------------------------------------------------------------------------------- ```typescript import axios from "axios"; import { UNTAPPED_API_BASE, UNTAPPED_API_INFO } from "../constants.js"; import { isUntappdApiError } from "../libs/guards.js"; import { UntappdBeerInfoResult } from "../types/untappedApi.js"; import { config } from "../index.js"; export async function GetBeerInfo(bid: string) { try { const response = await axios.get<UntappdBeerInfoResult>( `${UNTAPPED_API_BASE}${UNTAPPED_API_INFO}/${bid}`, { params: { client_id: config.untappd.clientId, client_secret: config.untappd.clientSecret, }, }, ); return response.data; } catch (e: unknown) { if (axios.isAxiosError(e) && e.response) { throw new Error(`HTTP error! status: ${e.response.status}`); } if (e instanceof Error) { return e.message; } if (isUntappdApiError(e)) { return e.meta.error_detail; } return "An unknown error occurred"; } } ``` -------------------------------------------------------------------------------- /src/api/getBreweryInfo.ts: -------------------------------------------------------------------------------- ```typescript import axios from "axios"; import { UNTAPPED_API_BASE, UNTAPPED_API_BREWERY_INFO, UNTAPPED_API_INFO, } from "../constants.js"; import { isUntappdApiError } from "../libs/guards.js"; import { UntappdBreweryInfoResult } from "../types/untappedApi.js"; import { config } from "../index.js"; export async function GetBreweryInfo(breweryId: string) { try { const response = await axios.get<UntappdBreweryInfoResult>( `${UNTAPPED_API_BASE}${UNTAPPED_API_BREWERY_INFO}/${breweryId}`, { params: { client_id: config.untappd.clientId, client_secret: config.untappd.clientSecret, }, }, ); return response.data; } catch (e: unknown) { if (axios.isAxiosError(e) && e.response) { throw new Error(`HTTP error! status: ${e.response.status}`); } if (e instanceof Error) { return e.message; } if (isUntappdApiError(e)) { return e.meta.error_detail; } return "An unknown error occurred"; } } ``` -------------------------------------------------------------------------------- /src/api/getBeerSearch.ts: -------------------------------------------------------------------------------- ```typescript import axios from "axios"; import { UNTAPPED_API_SEARCH, UNTAPPED_API_BASE } from "../constants.js"; import { isUntappdApiError } from "../libs/guards.js"; import { UntappdBeerSearchResult } from "../types/untappedApi.js"; import { config } from "../index.js"; export async function getBeerSearch(query: string) { try { const response = await axios.get<UntappdBeerSearchResult>( `${UNTAPPED_API_BASE}${UNTAPPED_API_SEARCH}`, { params: { q: query, client_id: config.untappd.clientId, client_secret: config.untappd.clientSecret, }, }, ); return response.data; } catch (e: unknown) { if (axios.isAxiosError(e) && e.response) { throw new Error( `HTTP error! status: ${e.response.status}: ${JSON.stringify(e)}`, ); } if (e instanceof Error) { return e.message; } if (isUntappdApiError(e)) { return e.meta.error_detail; } return "An unknown error occurred"; } } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { getBeerSearch } from "./api/getBeerSearch.js"; import { formatUntappdBeerInfoResult, formatUntappdBeerSearchResult, formatUntappdBreweryInfoResult, } from "./libs/format.js"; import dotenv from "dotenv"; import { GetBeerInfo } from "./api/getBeerInfo.js"; import { GetBreweryInfo } from "./api/getBreweryInfo.js"; dotenv.config(); export const config = { untappd: { clientId: process.env.UNTAPPED_API_CLIENT_ID, clientSecret: process.env.UNTAPPED_API_CLIENT_SECRET, }, }; // Create server instance const server = new McpServer({ name: "untapped", version: "1.0.0", }); // Register untapped tools server.tool( "Beer_Search", "Search beers on untapped", { beer: z.string().describe("The name of the beer you want to search"), }, async ({ beer }) => { const beersData = await getBeerSearch(beer); if (!beersData) { return { content: [ { type: "text", text: "Failed to retrieve untapped beer data.", }, ], }; } if (typeof beersData === "string") { return { content: [ { type: "text", text: `Failed to retrieve untapped beer data: ${beersData}`, }, ], }; } const formattedBeerData = formatUntappdBeerSearchResult(beersData); return { content: [ { type: "text", text: formattedBeerData, }, ], }; }, ); server.tool( "Beer_Info", "Get detailed info of a beer.", { bid: z .string() .describe( "Beer ID (string): The 'bid' can be retrieved from 'Beer Search'.", ), }, async ({ bid }) => { const beerInfoData = await GetBeerInfo(bid); if (!beerInfoData) { return { content: [ { type: "text", text: "Failed to retrieve untapped beer info data.", }, ], }; } if (typeof beerInfoData === "string") { return { content: [ { type: "text", text: `Failed to retrieve untapped beer info data: ${beerInfoData}`, }, ], }; } const formattedBeerInfoData = formatUntappdBeerInfoResult(beerInfoData); return { content: [ { type: "text", text: formattedBeerInfoData, }, ], }; }, ); server.tool( "Brewery_Info", "Get detailed info of a brewery.", { brewery_id: z .string() .describe( "brewery_id (string): The 'brewery_id' can be retrieved from a beer.", ), }, async ({ brewery_id }) => { const breweryInfoData = await GetBreweryInfo(brewery_id); if (!breweryInfoData) { return { content: [ { type: "text", text: "Failed to retrieve untapped brewery info data.", }, ], }; } if (typeof breweryInfoData === "string") { return { content: [ { type: "text", text: `Failed to retrieve untapped brewery info data: ${breweryInfoData}`, }, ], }; } const formattedBreweryInfoData = formatUntappdBreweryInfoResult(breweryInfoData); return { content: [ { type: "text", text: formattedBreweryInfoData, }, ], }; }, ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Untapped MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); }); ``` -------------------------------------------------------------------------------- /src/types/untappedApi.ts: -------------------------------------------------------------------------------- ```typescript // Common types // Contact information for brewery interface UntappdContact { twitter?: string; facebook?: string; instagram?: string; url?: string; } // Location information for brewery interface UntappdLocation { brewery_city?: string; brewery_state?: string; venue_address?: string; venue_city?: string; venue_state?: string; lat?: number; lng?: number; } // Brewery details interface UntappdBrewery { brewery_id: number; brewery_name: string; brewery_slug?: string; brewery_label: string; country_name: string; contact: UntappdContact; location: UntappdLocation; brewery_active?: number; beer_count?: number; // Only in breweries section } // Beer details interface UntappdBeer { bid: number; beer_name: string; beer_label: string; beer_abv: number; beer_ibu: number; beer_description: string; created_at: string; beer_style: string; auth_rating: number; wish_list: boolean; in_production?: number; beer_slug?: string; beer_style_id?: number; beer_active?: number; is_in_production?: number; is_vintage?: number; is_variant?: number; is_homebrew?: number; rating_count?: number; rating_score?: number; } // Beer stats interface UntappdBeerStats { total_count: number; monthly_count: number; total_user_count: number; user_count: number; } // User information interface UntappdUser { uid: number; user_name: string; first_name: string; last_name: string; user_avatar: string; relationship: string; is_private: number; } // Venue category interface UntappdVenueCategory { category_name: string; category_id: string; is_primary: boolean; } // Venue categories section interface UntappdVenueCategories { count: number; items: UntappdVenueCategory[]; } // Foursquare venue info interface UntappdFoursquareVenue { foursquare_id: string; foursquare_url: string; } // Venue icon interface UntappdVenueIcon { sm: string; md: string; lg: string; } // Venue information interface UntappdVenue { venue_id: number; venue_name: string; primary_category: string; parent_category_id: string; categories: UntappdVenueCategories; location: UntappdLocation; contact: UntappdContact; private_venue: boolean; foursquare: UntappdFoursquareVenue; venue_icon: UntappdVenueIcon; } // Photo information interface UntappdPhoto { photo_img_sm: string; photo_img_md: string; photo_img_lg: string; photo_img_og: string; } // Media item for beer export interface UntappdMediaItem { photo_id: number; photo: UntappdPhoto; created_at: string; checkin_id: number; beer: UntappdBeer; brewery: UntappdBrewery; user: UntappdUser; venue: UntappdVenue[]; } // Media section export interface UntappdMedia { count: number; items: UntappdMediaItem | UntappdMediaItem[]; // API inconsistently returns object or array } // Similar beer item interface UntappdSimilarBeerItem { rating_score: number; beer: UntappdBeer; brewery: UntappdBrewery; friends: { items: any[]; // Can be more specific if needed count: number; }; } // Similar beers section interface UntappdSimilarBeers { count: number; items: UntappdSimilarBeerItem | UntappdSimilarBeerItem[]; // API inconsistently returns object or array } // Friends section interface UntappdFriends { count: number; items: any[]; // Can be more specific if needed } // Vintage item interface UntappdVintageItem { beer: { bid: number; beer_label: string; beer_slug: string; beer_name: string; is_vintage: number; is_variant: number; }; } // Vintages section interface UntappdVintages { count: number; items: UntappdVintageItem[]; } // Complete beer info export interface UntappdBeerInfo extends UntappdBeer { stats: UntappdBeerStats; brewery: UntappdBrewery; media: UntappdMedia; similar: UntappdSimilarBeers; friends: UntappdFriends; vintages: UntappdVintages; } // Beer item in search results export interface UntappdBeerItem { checkin_count: number; have_had: boolean; your_count: number; beer: UntappdBeer; brewery: UntappdBrewery; } // Brewery item in search results interface UntappdBreweryItem { brewery: UntappdBrewery; } // Beer section in response interface UntappdBeerSection { count: number; items: UntappdBeerItem[]; } // Brewery section in response interface UntappdBrewerySection { count: number; items: UntappdBreweryItem[]; } // Response wrapper with metadata (common to all Untappd API responses) export interface UntappdApiResponse<T> { meta: { code: number; response_time: { time: number; measure: string; }; error_detail?: string; error_type?: string; developer_friendly?: string; }; notifications: Record<string, unknown>; response: T; } // Untappd API Error Response export interface UntappdApiErrorResponse { meta: { code: number; error_detail: string; error_type: string; developer_friendly?: string; response_time: { time: number; measure: string; }; }; } // Complete beer info response export interface UntappdBeerInfoResponse { beer: UntappdBeerInfo; } // Complete search response export interface UntappdBeerSearchResponse { found: number; offset: number; limit: number; term: string; parsed_term: string; beers: UntappdBeerSection; homebrew: UntappdBeerSection; breweries: UntappdBrewerySection; } // Additional Untappd types for brewery information // Claimed status information for brewery interface UntappdClaimedStatus { is_claimed: boolean; claimed_slug: string; follow_status: boolean; follower_count: number; uid: number; mute_status: string; } // Brewery rating information interface UntappdBreweryRating { count: number; rating_score: number; } // Extended brewery statistics interface UntappdBreweryStats { total_count: number; unique_count: number; monthly_count: number; weekly_count: number; user_count: number; age_on_service: number; } // Brewery owners section interface UntappdBreweryOwners { count: number; items: any[]; // Could be more specific if needed } // Beer list item in brewery response interface UntappdBeerListItem { has_had: boolean; total_count: number; beer: UntappdBeer; brewery: UntappdBrewery; friends: any[]; // Could be more specific if needed } // Beer list section in brewery response interface UntappdBeerList { is_super: boolean; sort: string; filter: string; count: number; items: UntappdBeerListItem | UntappdBeerListItem[]; // API inconsistently returns object or array beer_count: number; } // Extended brewery information export interface UntappdBreweryInfo extends UntappdBrewery { brewery_in_production: number; is_independent: number; claimed_status: UntappdClaimedStatus; brewery_type: string; brewery_type_id: number; brewery_description: string; rating: UntappdBreweryRating; stats: UntappdBreweryStats; owners: UntappdBreweryOwners; media: UntappdMedia; beer_list: UntappdBeerList; } // Complete brewery info response export interface UntappdBreweryInfoResponse { brewery: UntappdBreweryInfo; } // Complete typed response for brewery info export type UntappdBreweryInfoResult = UntappdApiResponse<UntappdBreweryInfoResponse>; // Complete typed response for beer search export type UntappdBeerSearchResult = UntappdApiResponse<UntappdBeerSearchResponse>; // Complete typed response for beer info export type UntappdBeerInfoResult = UntappdApiResponse<UntappdBeerInfoResponse>; ``` -------------------------------------------------------------------------------- /src/libs/format.ts: -------------------------------------------------------------------------------- ```typescript import { UntappdBeerInfo, UntappdBeerInfoResult, UntappdBeerItem, UntappdBeerSearchResult, UntappdBreweryInfoResult, UntappdMediaItem, } from "../types/untappedApi.js"; export function formatUntappdBeerItem(beerItem: UntappdBeerItem): string { return [ `Basic beer item properties`, `---`, `checkin_count: ${beerItem.checkin_count}`, `have_had: ${beerItem.have_had}`, `your_count: ${beerItem.your_count}`, `Beer properties`, `---`, `bid: ${beerItem.beer.bid}`, `beer_name: ${beerItem.beer.beer_name}`, `beer_label: ${beerItem.beer.beer_label}`, `beer_abv: ${beerItem.beer.beer_abv}`, `beer_ibu: ${beerItem.beer.beer_ibu}`, `beer_description: ${beerItem.beer.beer_description}`, `created_at: ${beerItem.beer.created_at}`, `beer_style: ${beerItem.beer.beer_style}`, `auth_rating: ${beerItem.beer.auth_rating}`, `wish_list: ${beerItem.beer.wish_list}`, `Optional beer properties`, `---`, ...(beerItem.beer.in_production !== undefined ? [`in_production: ${beerItem.beer.in_production}`] : []), ...(beerItem.beer.beer_slug ? [`beer_slug: ${beerItem.beer.beer_slug}`] : []), ...(beerItem.beer.beer_style_id ? [`beer_style_id: ${beerItem.beer.beer_style_id}`] : []), ...(beerItem.beer.beer_active !== undefined ? [`beer_active: ${beerItem.beer.beer_active}`] : []), ...(beerItem.beer.is_in_production !== undefined ? [`is_in_production: ${beerItem.beer.is_in_production}`] : []), ...(beerItem.beer.is_vintage !== undefined ? [`is_vintage: ${beerItem.beer.is_vintage}`] : []), ...(beerItem.beer.is_variant !== undefined ? [`is_variant: ${beerItem.beer.is_variant}`] : []), ...(beerItem.beer.is_homebrew !== undefined ? [`is_homebrew: ${beerItem.beer.is_homebrew}`] : []), ...(beerItem.beer.rating_count !== undefined ? [`rating_count: ${beerItem.beer.rating_count}`] : []), ...(beerItem.beer.rating_score !== undefined ? [`rating_score: ${beerItem.beer.rating_score}`] : []), `Brewery properties`, `---`, `brewery_id: ${beerItem.brewery.brewery_id}`, `brewery_name: ${beerItem.brewery.brewery_name}`, `brewery_label: ${beerItem.brewery.brewery_label}`, `country_name: ${beerItem.brewery.country_name}`, `Optional brewery properties`, `---`, ...(beerItem.brewery.brewery_slug ? [`brewery_slug: ${beerItem.brewery.brewery_slug}`] : []), ...(beerItem.brewery.brewery_active !== undefined ? [`brewery_active: ${beerItem.brewery.brewery_active}`] : []), ...(beerItem.brewery.beer_count !== undefined ? [`brewery_beer_count: ${beerItem.brewery.beer_count}`] : []), `Brewery contact information`, `---`, ...(beerItem.brewery.contact?.twitter ? [`brewery_twitter: ${beerItem.brewery.contact.twitter}`] : []), ...(beerItem.brewery.contact?.facebook ? [`brewery_facebook: ${beerItem.brewery.contact.facebook}`] : []), ...(beerItem.brewery.contact?.instagram ? [`brewery_instagram: ${beerItem.brewery.contact.instagram}`] : []), ...(beerItem.brewery.contact?.url ? [`brewery_url: ${beerItem.brewery.contact.url}`] : []), `Brewery location`, `---`, ...(beerItem.brewery.location?.brewery_city ? [`brewery_city: ${beerItem.brewery.location.brewery_city}`] : []), ...(beerItem.brewery.location?.brewery_state ? [`brewery_state: ${beerItem.brewery.location.brewery_state}`] : []), ...(beerItem.brewery.location?.venue_address ? [`venue_address: ${beerItem.brewery.location.venue_address}`] : []), ...(beerItem.brewery.location?.venue_city ? [`venue_city: ${beerItem.brewery.location.venue_city}`] : []), ...(beerItem.brewery.location?.venue_state ? [`venue_state: ${beerItem.brewery.location.venue_state}`] : []), ...(beerItem.brewery.location?.lat !== undefined ? [`latitude: ${beerItem.brewery.location.lat}`] : []), ...(beerItem.brewery.location?.lng !== undefined ? [`longitude: ${beerItem.brewery.location.lng}`] : []), "+++ End", ].join("\n"); } export function formatUntappdBeerInfo(beerInfo: UntappdBeerInfo): string { return [ `Beer Information`, `---`, `bid: ${beerInfo.bid}`, `beer_name: ${beerInfo.beer_name}`, `beer_label: ${beerInfo.beer_label}`, `beer_abv: ${beerInfo.beer_abv}`, `beer_ibu: ${beerInfo.beer_ibu}`, `beer_description: ${beerInfo.beer_description}`, `created_at: ${beerInfo.created_at}`, `beer_style: ${beerInfo.beer_style}`, `auth_rating: ${beerInfo.auth_rating}`, `Optional Beer Properties`, `---`, ...(beerInfo.in_production !== undefined ? [`in_production: ${beerInfo.in_production}`] : []), ...(beerInfo.beer_slug ? [`beer_slug: ${beerInfo.beer_slug}`] : []), ...(beerInfo.beer_style_id ? [`beer_style_id: ${beerInfo.beer_style_id}`] : []), ...(beerInfo.beer_active !== undefined ? [`beer_active: ${beerInfo.beer_active}`] : []), ...(beerInfo.is_in_production !== undefined ? [`is_in_production: ${beerInfo.is_in_production}`] : []), ...(beerInfo.is_vintage !== undefined ? [`is_vintage: ${beerInfo.is_vintage}`] : []), ...(beerInfo.is_variant !== undefined ? [`is_variant: ${beerInfo.is_variant}`] : []), ...(beerInfo.is_homebrew !== undefined ? [`is_homebrew: ${beerInfo.is_homebrew}`] : []), ...(beerInfo.rating_count !== undefined ? [`rating_count: ${beerInfo.rating_count}`] : []), ...(beerInfo.rating_score !== undefined ? [`rating_score: ${beerInfo.rating_score}`] : []), `Beer Stats`, `---`, `total_count: ${beerInfo.stats.total_count}`, `monthly_count: ${beerInfo.stats.monthly_count}`, `total_user_count: ${beerInfo.stats.total_user_count}`, `user_count: ${beerInfo.stats.user_count}`, `Brewery Information`, `---`, `brewery_id: ${beerInfo.brewery.brewery_id}`, `brewery_name: ${beerInfo.brewery.brewery_name}`, `brewery_label: ${beerInfo.brewery.brewery_label}`, `country_name: ${beerInfo.brewery.country_name}`, `Optional Brewery Properties`, `---`, ...(beerInfo.brewery.brewery_slug ? [`brewery_slug: ${beerInfo.brewery.brewery_slug}`] : []), `Brewery Location`, `---`, ...(beerInfo.brewery.location?.brewery_city ? [`brewery_city: ${beerInfo.brewery.location.brewery_city}`] : []), ...(beerInfo.brewery.location?.brewery_state ? [`brewery_state: ${beerInfo.brewery.location.brewery_state}`] : []), ...(beerInfo.brewery.location?.venue_address ? [`venue_address: ${beerInfo.brewery.location.venue_address}`] : []), ...(beerInfo.brewery.location?.venue_city ? [`venue_city: ${beerInfo.brewery.location.venue_city}`] : []), ...(beerInfo.brewery.location?.venue_state ? [`venue_state: ${beerInfo.brewery.location.venue_state}`] : []), ...(beerInfo.brewery.location?.lat !== undefined ? [`latitude: ${beerInfo.brewery.location.lat}`] : []), ...(beerInfo.brewery.location?.lng !== undefined ? [`longitude: ${beerInfo.brewery.location.lng}`] : []), `Media Information`, `---`, `media_count: ${beerInfo.media.count}`, `Similar Beers`, `---`, `similar_beers_count: ${beerInfo.similar.count}`, `Friends Information`, `---`, `friends_count: ${beerInfo.friends.count}`, `Vintages Information`, `---`, `vintages_count: ${beerInfo.vintages.count}`, ...(beerInfo.vintages.count > 0 ? [`Has vintage versions available`] : []), "Media Information", Array.isArray(beerInfo.media.items) ? beerInfo.media.items.map((m) => formatUntappdMediaItem(m)) : formatUntappdMediaItem(beerInfo.media.items), `+++ End`, ].join("\n"); } export function formatUntappdMediaItem(item: UntappdMediaItem): string { return [ `Media Item Information`, `---`, `created_at: ${item.created_at}`, `Beer Information`, `---`, `bid: ${item.beer.bid}`, `beer_name: ${item.beer.beer_name}`, `beer_style: ${item.beer.beer_style}`, `beer_abv: ${item.beer.beer_abv}`, `Brewery Information`, `---`, `brewery_id: ${item.brewery.brewery_id}`, `brewery_name: ${item.brewery.brewery_name}`, `country_name: ${item.brewery.country_name}`, `Venue Information`, `---`, ...(Array.isArray(item.venue) && item.venue.length > 0 ? [ `venue_id: ${item.venue[0].venue_id}`, `venue_name: ${item.venue[0].venue_name}`, `primary_category: ${item.venue[0].primary_category}`, ...(item.venue[0].location?.venue_city ? [`venue_city: ${item.venue[0].location.venue_city}`] : []), ...(item.venue[0].location?.venue_state ? [`venue_state: ${item.venue[0].location.venue_state}`] : []), ] : [`No venue information available`]), `+++ End`, ].join("\n"); } export function formatUntappdBeerSearchResult( result: UntappdBeerSearchResult, ): string { const payload: string[] = []; result.response.beers.items.forEach((beer: UntappdBeerItem) => { payload.push(formatUntappdBeerItem(beer)); }); return payload.join("\n"); } export function formatUntappdBeerInfoResult( result: UntappdBeerInfoResult, ): string { return formatUntappdBeerInfo(result.response.beer); } export function formatUntappdBreweryInfoResult( result: UntappdBreweryInfoResult, ): string { const breweryInfo = result.response.brewery; return [ `Brewery Information`, `---`, `brewery_id: ${breweryInfo.brewery_id}`, `brewery_name: ${breweryInfo.brewery_name}`, `country_name: ${breweryInfo.country_name}`, `brewery_in_production: ${breweryInfo.brewery_in_production}`, `is_independent: ${breweryInfo.is_independent}`, ...(breweryInfo.brewery_slug ? [`brewery_slug: ${breweryInfo.brewery_slug}`] : []), ...(breweryInfo.brewery_type ? [`brewery_type: ${breweryInfo.brewery_type}`] : []), ...(breweryInfo.brewery_type_id ? [`brewery_type_id: ${breweryInfo.brewery_type_id}`] : []), ...(breweryInfo.brewery_description ? [`brewery_description: ${breweryInfo.brewery_description}`] : []), `beer_count: ${breweryInfo.beer_count}`, `Contact Information`, `---`, ...(breweryInfo.contact?.url ? [`website: ${breweryInfo.contact.url}`] : []), `Location`, `---`, ...(breweryInfo.location?.venue_address ? [`address: ${breweryInfo.location.venue_address}`] : []), ...(breweryInfo.location?.brewery_city ? [`city: ${breweryInfo.location.brewery_city}`] : []), ...(breweryInfo.location?.brewery_state ? [`state: ${breweryInfo.location.brewery_state}`] : []), ...(breweryInfo.location?.lat !== undefined ? [`latitude: ${breweryInfo.location.lat}`] : []), ...(breweryInfo.location?.lng !== undefined ? [`longitude: ${breweryInfo.location.lng}`] : []), `Rating`, `---`, `rating_count: ${breweryInfo.rating.count}`, `rating_score: ${breweryInfo.rating.rating_score}`, `Statistics`, `---`, `total_check_ins: ${breweryInfo.stats.total_count}`, `unique_users: ${breweryInfo.stats.unique_count}`, `monthly_check_ins: ${breweryInfo.stats.monthly_count}`, `weekly_check_ins: ${breweryInfo.stats.weekly_count}`, `user_count: ${breweryInfo.stats.user_count}`, `age_on_service: ${breweryInfo.stats.age_on_service}`, `Beer List`, `---`, `is_super: ${breweryInfo.beer_list.is_super}`, `sort: ${breweryInfo.beer_list.sort || "default"}`, `filter: ${breweryInfo.beer_list.filter}`, `beer_count: ${breweryInfo.beer_list.beer_count}`, `displayed_beers: ${breweryInfo.beer_list.count}`, `Brewery Beers`, `---`, ...(breweryInfo.beer_list.count > 0 ? Array.isArray(breweryInfo.beer_list.items) ? breweryInfo.beer_list.items.map((beer) => [ `bid: ${beer.beer.bid}`, `beer_name: ${beer.beer.beer_name}`, `beer_style: ${beer.beer.beer_style}`, `beer_abv: ${beer.beer.beer_abv}`, `beer_ibu: ${beer.beer.beer_ibu}`, `beer_description: ${beer.beer.beer_description}`, `rating_score: ${beer.beer.rating_score}`, `rating_count: ${beer.beer.rating_count}`, "\n", ]) : "" : [`No beers available to display`]), `+++ End`, ].join("\n"); } ```