@atlas-kitchen/atlas-mcp
Version:
Model Context Protocol server for Atlas restaurant management system - enables Claude to interact with restaurant orders, menus, and reports
1,142 lines • 29.2 kB
JavaScript
import { GraphQLClient } from 'graphql-request';
export class AtlasClient {
restaurantClient;
accountClient;
authManager;
baseUrl;
constructor(authManager) {
this.authManager = authManager;
this.baseUrl = process.env.ATLAS_GRAPHQL_ENDPOINT || 'https://api.atlas.kitchen';
// Create separate clients for different graph contexts
this.restaurantClient = new GraphQLClient(`${this.baseUrl}/v1/restaurants/graphql`);
this.accountClient = new GraphQLClient(`${this.baseUrl}/v1/accounts/graphql`);
}
async request(query, variables, context = 'restaurants') {
const client = context === 'accounts' ? this.accountClient : this.restaurantClient;
try {
// Set headers for this request
const headers = this.authManager.getHeaders();
return await client.request(query, variables, headers);
}
catch (error) {
// Handle GraphQL errors
if (error.response?.errors) {
const graphqlError = error.response.errors[0];
throw new Error(`GraphQL Error: ${graphqlError.message}`);
}
throw error;
}
}
async login(email, password) {
const query = `
mutation AccountLogin($email: String!, $password: String!) {
accountLogin(input: { email: $email, password: $password }) {
refreshToken
accessToken
account {
id
email
}
merchants {
identifier
}
}
}
`;
const response = await this.request(query, { email, password }, 'accounts');
return response.accountLogin;
}
async logout() {
const query = `
mutation AccountLogout {
accountLogout {
success
}
}
`;
const response = await this.request(query, {}, 'accounts');
return response.accountLogout;
}
async refreshToken(refreshToken) {
// Temporarily set the refresh token as the access token for this request
const currentToken = this.authManager.getAccessToken();
this.authManager.setTokens({ accessToken: refreshToken, refreshToken: refreshToken });
try {
const query = `
mutation GenerateAccessToken {
accountGenerateAccessToken {
accessToken
refreshToken
account {
id
email
name
username
isVerified
requireSetPassword
merchants {
id
identifier
name
}
merchantAccounts {
id
merchantId
role
}
}
merchants {
id
identifier
name
}
}
}
`;
const response = await this.request(query, {}, 'accounts');
return response.accountGenerateAccessToken;
}
finally {
// Restore the original token if the refresh failed
if (currentToken) {
this.authManager.setTokens({ accessToken: currentToken, refreshToken: refreshToken });
}
}
}
async getOrders(filters = {}) {
const query = `
query GetOrders($filter: OrderFilter) {
allOrders(filter: $filter) {
totalCount
page
perPage
metadata
orders {
state
identifier
servingDate
createdAt
addressLine1
addressLine2
postalCode
brand {
label
}
timeslotRange
fulfilmentType
fulfilmentSubType
fulfilmentState
isContactless
isCutleryRequired
notes
paymentBreakdown {
total
totalIncludingTax
tip
}
table {
name
}
outlet {
label
labelForPickup
}
id
sourceLabel
servedByName
externalOrderShortCode
topLevelItems {
name
quantity
}
contactName
contactEmail
contactNumber
isGift
recipientName
recipientContactNumber
giftMessage
currentTrip {
externalLogisticsId
externalLogisticsCode
externalLogisticsType
merchantLogisticsCost
}
orderPayments {
paymentType {
label
}
}
}
}
}
`;
// Transform filters to match OrderFilter structure
const filter = {};
if (filters.startDate || filters.endDate) {
filter.dateRange = {};
if (filters.startDate)
filter.dateRange.startDate = filters.startDate;
if (filters.endDate)
filter.dateRange.endDate = filters.endDate;
}
if (filters.state)
filter.state = filters.state;
if (filters.first)
filter.perPage = filters.first;
if (filters.after)
filter.page = filters.after; // This might need adjustment based on actual cursor structure
const response = await this.request(query, { filter });
return response.allOrders;
}
async getOrder(orderId) {
const query = `
query GetOrder($id: Int!) {
order(id: $id) {
id
identifier
state
servingDate
prepareBy
isContactless
isCutleryRequired
fulfilmentType
fulfilmentSubType
fulfilmentState
notes
buzzerIdentifier
chargeId
stripeAccountId
isAdyenEnabled
sourceLabel
externalOrderShortCode
contactName
contactNumber
contactEmail
contactAccessCode
isGift
recipientName
recipientContactNumber
giftMessage
promoCode
stationId
isPostTaxDiscount
recipientOrganisationName
invoices {
id
paymentStatus
totalPaid
totalRefunded
amountOutstanding
invoicePayments {
id
createdAt
amount
paymentType {
label
}
reference
referenceType
notes
invoicePaymentRefunds {
id
createdAt
amount
reason
reference
referenceType
paymentType {
label
}
}
}
}
pointProgram {
currencyNames
isCustomCurrency
label
type
}
promotion {
id
type
label
description
valueType
}
orderDiscounts {
id
discountType
value
quantity
reason
promoCode
targetType
sourceType
sourceId
}
createdAt
externalOrderedAt
payAtStation
serveByOverride
servedByName
table {
name
}
address {
id
line1
line2
postalCode
notes
}
brand {
label
}
timeslotId
timeslotType
timeslotRange
currentTrip {
externalLogisticsId
externalLogisticsCode
externalLogisticsType
externalLogisticsTrackingUrl
isCancelled
logisticsDriverInfo
logisticsStatus
}
topLevelItems {
id
name
quantity
notes
subtotal
itemModifierGroupId
paid
amountPaid
discount
refunds
refundableAmount
unitPriceFractional
unitLabel
customIdentifier
item {
id
isConfigurable
idBreadcrumb
horizontalImageUrl
}
isOpenItem
adjustmentReason {
id
label
}
sortedSubItems {
id
idBreadcrumb
label
subItems {
id
name
quantity
subtotal
unitLabel
customIdentifier
item {
id
isConfigurable
idBreadcrumb
horizontalImageUrl
}
adjustmentReason {
id
label
}
sortedSubItems {
id
idBreadcrumb
label
subItems {
id
name
quantity
subtotal
unitLabel
customIdentifier
item {
id
isConfigurable
idBreadcrumb
horizontalImageUrl
}
adjustmentReason {
id
label
}
}
}
}
}
subItems {
id
modifierId
itemModifierGroupId
name
quantity
subtotal
unitLabel
perUnitQuantity
customIdentifier
adjustmentReason {
id
label
}
unitPriceFractional
subItems {
id
modifierId
itemModifierGroupId
name
quantity
subtotal
unitLabel
perUnitQuantity
customIdentifier
adjustmentReason {
id
label
}
unitPriceFractional
}
}
appliedDiscounts {
id
value
percentValue
quantity
reason
}
}
user {
id
userId
name
email
mobileNumber
isGuest
addresses {
id
line1
line2
nearestOutlet {
id
label
labelForPickup
}
}
}
outlet {
id
label
labelForPickup
useAutoBookLogistics
stations {
id
name
posEnabled
printers {
id
name
}
paymentTerminals {
id
}
}
}
paymentBreakdown {
subtotal
merchantTotalIncludingTax
merchantTax
totalIncludingTax
tax
taxInclusivePrices
cashVouchersAmount
surcharge
surchargeLabel
serviceCharge
serviceChargeDiscount
donationAmount
donationLabel
pointsAmount
pointsValue
tip
discount
platformDiscount
adminDiscount
deliveryFee
platformDeliveryFee
amountUnpaid
rounding
}
minimumVehicleType
minimumVehicleTypeItemLabel
minimumVehicleTypeOrderValue
paymentLinkId
paymentLinkUrl
paymentTypeId
paymentTypeLabel
paymentType {
id
label
subType
paymentTypeImageUrl
paymentMethodId
}
cart {
id
bills {
id
capturedAt
total
paymentType {
id
label
subType
paymentTypeImageUrl
paymentMethodId
}
}
capturedAmount
uncapturedAmount
cartCashVouchers {
id
quantity
voucherReference
cashVoucher {
id
label
value
code
}
}
}
orderPayments {
id
amount
tip
isCaptured
captureId
paymentType {
id
label
subType
paymentTypeImageUrl
paymentMethodId
}
cardLast4
orderRefunds {
id
type
amount
reason
isSuccessful
responseReason
}
}
}
}
`;
// Convert string ID to integer if needed
const id = parseInt(orderId, 10);
const response = await this.request(query, { id });
return response.order;
}
async getCart(cartId) {
const query = `
query GetPosCartOptimised($cartId: Int!) {
getPosCartOptimised(cartId: $cartId) {
id
archived
isCheckedOut
promoCode
promoCodeError
platform
payAtStation
posUserValidated
promotion {
id
type
label
description
value
valueType
}
cartDiscounts {
id
discountType
value
quantity
reason
promoCode
targetType
sourceType
sourceId
}
cartCashVouchers {
id
cashVoucherId
voucherReference
quantity
cashVoucher {
id
code
value
}
}
cashVouchers {
id
code
value
}
donationId
isPostTaxDiscount
serviceChargeable
paymentBreakdown {
deliveryFee
surcharge
surchargeLabel
discount
donationAmount
cashVouchersAmount
tax
subtotal
serviceCharge
serviceChargeDiscount
totalIncludingTax
taxInclusivePrices
rounding
cashRounding
pointsValue
pointsAmount
usePoints
}
notes
buzzerIdentifier
userId
user {
id
userId
name
email
mobileNumber
isGuest
}
pax
paxKids
paymentTypeId
paymentTypeLabel
lastBilledAt
topLevelItems {
id
name
quantity
notes
subtotal
serviceChargeable
itemModifierGroupId
sentAt
unitLabel
customIdentifier
seatId
cartItemGroup {
id
label
createdAt
groupType
}
adjustmentReason {
id
label
}
appliedDiscounts {
id
value
percentValue
quantity
reason
sourceType
sourceId
}
itemId
item {
id
idBreadcrumb
horizontalImageUrl
}
isOpenItem
sortedSubItems {
idBreadcrumb
label
subItems {
id
itemId
name
notes
quantity
subtotal
unitLabel
customIdentifier
adjustmentReason {
id
label
}
sortedSubItems {
idBreadcrumb
label
subItems {
id
itemId
name
notes
quantity
subtotal
unitLabel
customIdentifier
adjustmentReason {
id
label
}
}
}
}
}
subItems {
id
itemId
modifierId
itemModifierGroupId
name
notes
quantity
subtotal
unitLabel
customIdentifier
adjustmentReason {
id
label
}
subItems {
id
itemId
modifierId
itemModifierGroupId
name
notes
quantity
subtotal
unitLabel
customIdentifier
adjustmentReason {
id
label
}
}
}
}
qrHash
tableId
tableName
fulfilmentType
outletId
servingDate
timeslotType
timeslot {
id
rangeLabel
}
address {
id
line1
line2
postalCode
notes
}
hasUnsentItems
hasSentItems
hasHeldItems
bills {
id
billItemsCount
capturedAt
discount
donation
paymentTypeId
paymentType {
id
label
}
tax
total
type
billItems {
id
cartItemId
cartItem {
id
name
quantity
notes
}
quantity
total
}
}
capturedAmount
uncapturedAmount
splitAmount
unsplitAmount
uncapturedCartItems {
id
name
quantity
subtotal
}
}
}
`;
// Convert string ID to integer if needed
const id = parseInt(cartId, 10);
const response = await this.request(query, { cartId: id });
return response.getPosCartOptimised;
}
async getOpenPosCarts() {
const query = `
query GetOpenPosCarts {
allOpenPosCarts {
id
paymentTypeId
paymentTypeLabel
platform
payAtStation
buzzerIdentifier
fulfilmentType
tableId
tableName
hasUnsentItems
lastBilledAt
hasSentItems
hasHeldItems
notes
pax
paxKids
createdAt
subtotal
user {
id
name
}
}
}
`;
const response = await this.request(query);
return response.allOpenPosCarts;
}
async getMenus(outletId, servingDate, timeslotType) {
const query = `
query GetMenus($outletId: Int, $servingDate: ISO8601Date, $timeslotType: String) {
menus(outletId: $outletId, servingDate: $servingDate, timeslotType: $timeslotType) {
id
state
label
identifier
startDate
endDate
createdAt
updatedAt
brandId
tagList
imageUrl
brand {
label
}
outletBrands {
outletId
brandId
grabFoodMerchantId
foodpandaMerchantId
}
brandOutletMenus {
outletId
brandId
}
}
}
`;
const response = await this.request(query, { outletId, servingDate, timeslotType });
return response.menus;
}
async getOptimizedMenus(outletId, servingDate) {
const query = `
query GetOptimisedMenus($outletId: Int!, $servingDate: ISO8601Date!) {
menusOptimised(outletId: $outletId, servingDate: $servingDate) {
id
label
startDate
endDate
sections {
id
label
description
displayOrder
items {
id
label
description
displayOrder
unitPriceFractional
currency
isConfigurable
horizontalImageUrl
idBreadcrumb
modifierGroups {
id
label
selectionRequiredMin
selectionRequiredMax
displayOrder
isFixed
maxQuantityPerModifier
idBreadcrumb
modifiers {
id
label
description
displayOrder
itemId
unitPriceFractional
currency
isConfigurable
horizontalImageUrl
idBreadcrumb
defaultQuantity
unitLabel
perUnitQuantity
modifierGroups {
id
label
selectionRequiredMin
selectionRequiredMax
displayOrder
isFixed
maxQuantityPerModifier
idBreadcrumb
modifiers {
id
label
description
displayOrder
itemId
unitPriceFractional
currency
isConfigurable
horizontalImageUrl
idBreadcrumb
defaultQuantity
unitLabel
perUnitQuantity
}
}
}
}
}
subSections {
id
label
description
displayOrder
items {
id
label
description
displayOrder
unitPriceFractional
currency
isConfigurable
horizontalImageUrl
idBreadcrumb
modifierGroups {
id
label
selectionRequiredMin
selectionRequiredMax
displayOrder
isFixed
maxQuantityPerModifier
idBreadcrumb
modifiers {
id
label
description
displayOrder
itemId
unitPriceFractional
currency
isConfigurable
horizontalImageUrl
idBreadcrumb
defaultQuantity
unitLabel
perUnitQuantity
modifierGroups {
id
label
selectionRequiredMin
selectionRequiredMax
displayOrder
isFixed
maxQuantityPerModifier
idBreadcrumb
modifiers {
id
label
description
displayOrder
itemId
unitPriceFractional
currency
isConfigurable
horizontalImageUrl
idBreadcrumb
defaultQuantity
unitLabel
perUnitQuantity
}
}
}
}
}
}
}
}
}
`;
const response = await this.request(query, { outletId, servingDate });
return response.menusOptimised;
}
async getItems(filter) {
const query = `
query GetItems($filter: ItemFilter) {
allItems(filter: $filter) {
totalCount
page
perPage
items {
id
type
archivedAt
identifier
label
internalLabel
description
unitPriceFractional
unitCostFractional
unitLabel
perUnitQuantity
currency
serviceChargeable
isConfigurable
horizontalImageUrl
brandId
kitchenTagList
tagList
reportCategory
promotionalLabelText
promotionalLabelFontColor
promotionalLabelBgColor
availabilityConditionGroups {
id
startDate
endDate
hidden
disabledReason
availableByDefault
availabilityConditions {
id
outletIds
daysOfWeek
fulfilmentTypes
startTime
endTime
}
}
}
}
}
`;
const response = await this.request(query, { filter });
return response.allItems;
}
async getSalesReport(filters, dateRange) {
const query = `
query GetSalesSummaryReport($filters: ReportFilter, $dateRange: DateRange, $dateRangeType: DateRangeField) {
getSalesSummaryReport(filters: $filters, dateRange: $dateRange, dateRangeType: $dateRangeType) {
metadata
salesSummary
netSalesByDay
netSalesByHour
paymentSummary
underpaymentSummary
outletSummary
diningOptionSummary
tableSummary
orderSourceSummary
promotionSummary
brandSummary
salesCategorySummary
shiftSummary
tipSummary
}
}
`;
const response = await this.request(query, { filters, dateRange, dateRangeType: "ORDER_SERVING_DATE" });
return response.getSalesSummaryReport;
}
async getProductInsights(params) {
const query = `
query GetProductInsights(
$brandIds: [Int!]!
$outletIds: [Int!]!
$itemTypes: [String!]!
$fulfilmentTypes: [String!]!
$servingDateBetween: DateRange!
$sources: [String!]
$itemTagIds: [Int!]
$searchQuery: String
$itemsToVisualize: [String!]
) {
getProductInsights(
brandIds: $brandIds
outletIds: $outletIds
itemTypes: $itemTypes
fulfilmentTypes: $fulfilmentTypes
servingDateBetween: $servingDateBetween
sources: $sources
itemTagIds: $itemTagIds
searchQuery: $searchQuery
itemsToVisualize: $itemsToVisualize
) {
top20ProductsByCount
top20ProductsBySales
bottom20ProductsByCount
bottom20ProductsBySales
allItemsByPopularity
allProductsPurchased
rawData
}
}
`;
const response = await this.request(query, params);
return response.getProductInsights;
}
}
//# sourceMappingURL=client.js.map