# Deployment Guide
## Overview
The Shopify Agentic MCP Gateway supports two deployment modes:
1. **Local (stdio)** -- For development and testing with Claude Desktop
2. **AWS Lambda** -- For production serverless deployment
This guide covers the full production deployment to AWS Lambda.
---
## Prerequisites
- **Node.js** >= 22.0.0
- **AWS CLI** configured with appropriate IAM permissions
- **Serverless Framework** (`npm install -g serverless`)
- **Shopify Partner Account** with a Shopify app configured for Storefront and Admin API access
- **ES256 Key Pair** for AP2 mandate signing (see [Key Generation](#key-generation))
### Required IAM Permissions
The deploying IAM user/role needs permissions for:
- Lambda function management
- API Gateway management
- DynamoDB table creation and management
- CloudFormation stack management
- IAM role creation (for Lambda execution role)
- CloudWatch Logs
---
## Step-by-Step AWS Lambda Deployment
### 1. Clone and Install
```bash
git clone https://github.com/your-org/shopify-agentic-mcp.git
cd shopify-agentic-mcp
npm install
```
### 2. Generate AP2 Key Pair
```bash
node -e "
import { MandateSigner } from './dist/ap2/signer.js';
const kp = await MandateSigner.generateKeyPair();
console.log('Private Key:', JSON.stringify(kp.privateKey));
console.log('Public Key:', JSON.stringify(kp.publicKey));
console.log('Key ID:', kp.kid);
" --input-type=module
```
Save the private key securely. The public key will be published at your JWKS endpoint.
### 3. Configure Environment Variables
```bash
cp .env.example .env
```
Fill in all required values:
```env
# Shopify
SHOPIFY_API_KEY=shpka_xxxxxxxxxxxxx
SHOPIFY_API_SECRET=shpss_xxxxxxxxxxxxx
SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
SHOPIFY_ACCESS_TOKEN=shpat_xxxxxxxxxxxxx
SHOPIFY_STOREFRONT_TOKEN=xxxxxxxxxxxxx
# AP2
AP2_SIGNING_PRIVATE_KEY={"kty":"EC","crv":"P-256","x":"...","y":"...","d":"...","kid":"ap2_xxx"}
AP2_VERIFICATION_PUBLIC_KEY={"kty":"EC","crv":"P-256","x":"...","y":"...","kid":"ap2_xxx"}
# Gateway
GATEWAY_BASE_URL=https://your-api-id.execute-api.us-east-1.amazonaws.com
FEE_RATE=0.005
FEE_WALLET_ADDRESS=your_wallet_address
# AWS
AWS_REGION=us-east-1
# Logging
LOG_LEVEL=info
```
### 4. Build
```bash
npm run build
```
This compiles TypeScript to `dist/` with source maps and declaration files.
### 5. Deploy
```bash
npm run deploy
```
Or directly with Serverless:
```bash
serverless deploy --stage prod
```
The deployment creates:
| Resource | Type | Purpose |
|----------|------|---------|
| `shopify-agentic-mcp-prod-ucpGateway` | Lambda Function | Main handler (Node.js 22.x, ARM64) |
| HTTP API Gateway | API Gateway v2 | Routes and CORS |
| `shopify-agentic-mcp-mandates-prod` | DynamoDB Table | AP2 mandate storage |
| `shopify-agentic-mcp-ledger-prod` | DynamoDB Table | Fee collection ledger |
| `shopify-agentic-mcp-sessions-prod` | DynamoDB Table | Checkout sessions (with TTL) |
### 6. Verify Deployment
```bash
# Get the API Gateway URL from deployment output
export API_URL=https://your-api-id.execute-api.us-east-1.amazonaws.com
# Test UCP profile discovery
curl -s $API_URL/.well-known/ucp | jq .
# Expected: UCPProfile JSON with version, services, transports, capabilities
```
---
## Environment Variables Reference
### Shopify (Required)
| Variable | Description | Example |
|----------|-------------|---------|
| `SHOPIFY_API_KEY` | Shopify app API key | `shpka_xxxxx` |
| `SHOPIFY_API_SECRET` | Shopify app API secret | `shpss_xxxxx` |
| `SHOPIFY_STORE_DOMAIN` | Shopify store domain | `store.myshopify.com` |
| `SHOPIFY_ACCESS_TOKEN` | Admin API access token | `shpat_xxxxx` |
| `SHOPIFY_STOREFRONT_TOKEN` | Storefront API token | `xxxxx` |
### AP2 (Required)
| Variable | Description | Example |
|----------|-------------|---------|
| `AP2_SIGNING_PRIVATE_KEY` | ES256 private key (JWK JSON string) | `{"kty":"EC",...,"d":"..."}` |
| `AP2_VERIFICATION_PUBLIC_KEY` | ES256 public key (JWK JSON string, optional -- derived from private if omitted) | `{"kty":"EC",...}` |
### Gateway (Optional)
| Variable | Default | Description |
|----------|---------|-------------|
| `GATEWAY_BASE_URL` | `http://localhost:3000` | Public URL of the deployed gateway |
| `FEE_RATE` | `0.005` | Platform fee rate (0.005 = 0.5%) |
| `FEE_WALLET_ADDRESS` | (empty) | Wallet address for fee collection |
### AWS (Deployment)
| Variable | Default | Description |
|----------|---------|-------------|
| `AWS_REGION` | `us-east-1` | AWS region for deployment |
| `DYNAMODB_TABLE_MANDATES` | `agentic-mcp-mandates` | DynamoDB table name for mandates |
| `DYNAMODB_TABLE_LEDGER` | `agentic-mcp-ledger` | DynamoDB table name for fee ledger |
| `DYNAMODB_TABLE_SESSIONS` | `agentic-mcp-sessions` | DynamoDB table name for checkout sessions |
### Logging
| Variable | Default | Description |
|----------|---------|-------------|
| `LOG_LEVEL` | `info` | Logging level (`debug`, `info`, `warn`, `error`) |
---
## CloudFront Setup for `/.well-known/ucp`
For production, place CloudFront in front of API Gateway to:
- Provide a custom domain (e.g., `gw.yourdomain.com`)
- Cache the UCP profile at the edge
- Terminate TLS with your own certificate
### CloudFront Configuration
```yaml
# CloudFront Distribution Settings
Origins:
- DomainName: your-api-id.execute-api.us-east-1.amazonaws.com
OriginPath: ""
CustomOriginConfig:
OriginProtocolPolicy: https-only
CacheBehaviors:
# Cache the UCP profile aggressively (changes rarely)
- PathPattern: "/.well-known/ucp"
CachePolicyId: "658327ea-f89d-4fab-a63d-7e88639e58f6" # CachingOptimized
TTL:
DefaultTTL: 3600 # 1 hour
MaxTTL: 86400 # 24 hours
# Do NOT cache API/MCP endpoints
- PathPattern: "/ucp/v1/*"
CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # CachingDisabled
- PathPattern: "/mcp"
CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # CachingDisabled
```
### Custom Domain Setup
1. Register a domain in Route 53 (or use an existing one).
2. Request an ACM certificate for `gw.yourdomain.com` in `us-east-1`.
3. Create a CloudFront distribution with the API Gateway origin.
4. Add a CNAME record: `gw.yourdomain.com` -> `dxxxxxxxxxx.cloudfront.net`.
5. Update `GATEWAY_BASE_URL` to `https://gw.yourdomain.com`.
6. Redeploy.
---
## Lambda Configuration Details
The `serverless.yml` configures the Lambda function with:
```yaml
provider:
runtime: nodejs22.x # Latest LTS Node.js
architecture: arm64 # Graviton2 (cheaper, faster)
memorySize: 512 # MB -- sufficient for JWS operations
timeout: 30 # seconds -- checkout execution may take time
```
### Routes
| Route | Method | Purpose |
|-------|--------|---------|
| `/.well-known/ucp` | GET | UCP Profile Discovery |
| `/ucp/v1/{proxy+}` | ANY | UCP REST API (catalog, cart, checkout, orders, negotiate) |
| `/mcp` | POST | MCP HTTP transport |
| `/a2a` | POST | Agent-to-Agent transport |
### DynamoDB Tables
All three tables use PAY_PER_REQUEST billing (no capacity planning needed):
**Mandates Table:**
- Primary key: `mandateId` (String)
- GSI: `checkoutIndex` on `checkoutId` (String)
**Ledger Table:**
- Primary key: `transactionId` (String)
**Sessions Table:**
- Primary key: `sessionId` (String)
- TTL: `ttl` attribute enabled (auto-cleanup of expired sessions)
---
## Monitoring and Logging
### CloudWatch Logs
All Lambda invocations are logged to CloudWatch. The structured logger produces JSON output:
```json
{
"level": "info",
"message": "execute_checkout: order confirmed",
"order_id": "order_abc123",
"total": 6023,
"currency": "USD",
"timestamp": "2026-02-18T10:06:30.000Z"
}
```
### Key Metrics to Monitor
| Metric | Source | Alert Threshold |
|--------|--------|-----------------|
| Lambda errors | CloudWatch Metrics | > 5 errors in 5 minutes |
| Lambda duration | CloudWatch Metrics | p99 > 10 seconds |
| Mandate verification failures | Application logs (`mandate chain invalid`) | > 10 in 1 hour |
| Guardrail blocks | Application logs (`guardrail_blocked`) | Any occurrence (investigate) |
| Fee collection failures | Application logs (`fee/order step failed`) | Any occurrence |
| DynamoDB throttling | DynamoDB Metrics | Any throttled requests |
### Recommended CloudWatch Alarms
```bash
# Create alarm for Lambda errors
aws cloudwatch put-metric-alarm \
--alarm-name "agentic-mcp-errors" \
--metric-name Errors \
--namespace AWS/Lambda \
--statistic Sum \
--period 300 \
--threshold 5 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=FunctionName,Value=shopify-agentic-mcp-prod-ucpGateway \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:ACCOUNT:alerts
```
### Log Insights Queries
**Find all failed checkouts:**
```
fields @timestamp, @message
| filter @message like /execute_checkout.*failed/
| sort @timestamp desc
| limit 50
```
**Fee collection summary:**
```
fields @timestamp, @message
| filter @message like /fee collected/
| parse @message "fee_amount: *," as fee_amount
| stats sum(fee_amount) as total_fees by bin(1d)
```
---
## Scaling Considerations
| Component | Scaling Strategy |
|-----------|-----------------|
| Lambda | Auto-scales to 1000 concurrent (default). Request increase for higher traffic. |
| DynamoDB | PAY_PER_REQUEST auto-scales. No capacity planning needed. |
| API Gateway | Managed, auto-scales. Default 10,000 requests/second. |
| CloudFront | Global edge, unlimited scale for cached content. |
For high-volume merchants, consider:
- Enabling DynamoDB Accelerator (DAX) for mandate lookups
- Increasing Lambda reserved concurrency
- Setting up multi-region deployment with Route 53 latency routing