├── .gitignore ├── README.md ├── example-desktop-config.json ├── package-lock.json ├── package.json ├── src └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | *.log 4 | .env* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amazon-fresh-server MCP Server 2 | 3 | A Model Context Protocol server 4 | 5 | This is a TypeScript-based MCP server that implements a simple notes system. It demonstrates core MCP concepts by providing: 6 | 7 | - Resources representing text notes with URIs and metadata 8 | - Tools for creating new notes 9 | - Prompts for generating summaries of notes 10 | 11 | ## Features 12 | 13 | ### Resources 14 | - List and access notes via `note://` URIs 15 | - Each note has a title, content and metadata 16 | - Plain text mime type for simple content access 17 | 18 | ### Tools 19 | - `create_note` - Create new text notes 20 | - Takes title and content as required parameters 21 | - Stores note in server state 22 | 23 | ### Prompts 24 | - `summarize_notes` - Generate a summary of all stored notes 25 | - Includes all note contents as embedded resources 26 | - Returns structured prompt for LLM summarization 27 | 28 | ## Development 29 | 30 | Install dependencies: 31 | ```bash 32 | npm install 33 | ``` 34 | 35 | Build the server: 36 | ```bash 37 | npm run build 38 | ``` 39 | 40 | For development with auto-rebuild: 41 | ```bash 42 | npm run watch 43 | ``` 44 | 45 | ## Installation 46 | 47 | To use with Claude Desktop, add the server config: 48 | 49 | On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 50 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json` 51 | 52 | ```json 53 | { 54 | "mcpServers": { 55 | "amazon-fresh-server": { 56 | "command": "/path/to/amazon-fresh-server/build/index.js" 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | ### Debugging 63 | 64 | Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script: 65 | 66 | ```bash 67 | npm run inspector 68 | ``` 69 | 70 | The Inspector will provide a URL to access debugging tools in your browser. 71 | -------------------------------------------------------------------------------- /example-desktop-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "amazon-fresh-server": { 4 | "command": "/Users/alecv/.nvm/versions/node/v20.11.0/bin/node", 5 | "args": [ 6 | "/Users/alecv/dev/newform/random/amazon-fresh-server/build/index.js" 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazon-fresh-server", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "amazon-fresh-server", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "@modelcontextprotocol/sdk": "0.6.0" 12 | }, 13 | "bin": { 14 | "amazon-fresh-server": "build/index.js" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^20.11.24", 18 | "typescript": "^5.3.3" 19 | } 20 | }, 21 | "node_modules/@modelcontextprotocol/sdk": { 22 | "version": "0.6.0", 23 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", 24 | "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", 25 | "dependencies": { 26 | "content-type": "^1.0.5", 27 | "raw-body": "^3.0.0", 28 | "zod": "^3.23.8" 29 | } 30 | }, 31 | "node_modules/@types/node": { 32 | "version": "20.17.8", 33 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.8.tgz", 34 | "integrity": "sha512-ahz2g6/oqbKalW9sPv6L2iRbhLnojxjYWspAqhjvqSWBgGebEJT5GvRmk0QXPj3sbC6rU0GTQjPLQkmR8CObvA==", 35 | "dev": true, 36 | "dependencies": { 37 | "undici-types": "~6.19.2" 38 | } 39 | }, 40 | "node_modules/bytes": { 41 | "version": "3.1.2", 42 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 43 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 44 | "engines": { 45 | "node": ">= 0.8" 46 | } 47 | }, 48 | "node_modules/content-type": { 49 | "version": "1.0.5", 50 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 51 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 52 | "engines": { 53 | "node": ">= 0.6" 54 | } 55 | }, 56 | "node_modules/depd": { 57 | "version": "2.0.0", 58 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 59 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 60 | "engines": { 61 | "node": ">= 0.8" 62 | } 63 | }, 64 | "node_modules/http-errors": { 65 | "version": "2.0.0", 66 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 67 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 68 | "dependencies": { 69 | "depd": "2.0.0", 70 | "inherits": "2.0.4", 71 | "setprototypeof": "1.2.0", 72 | "statuses": "2.0.1", 73 | "toidentifier": "1.0.1" 74 | }, 75 | "engines": { 76 | "node": ">= 0.8" 77 | } 78 | }, 79 | "node_modules/iconv-lite": { 80 | "version": "0.6.3", 81 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 82 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 83 | "dependencies": { 84 | "safer-buffer": ">= 2.1.2 < 3.0.0" 85 | }, 86 | "engines": { 87 | "node": ">=0.10.0" 88 | } 89 | }, 90 | "node_modules/inherits": { 91 | "version": "2.0.4", 92 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 93 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 94 | }, 95 | "node_modules/raw-body": { 96 | "version": "3.0.0", 97 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 98 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 99 | "dependencies": { 100 | "bytes": "3.1.2", 101 | "http-errors": "2.0.0", 102 | "iconv-lite": "0.6.3", 103 | "unpipe": "1.0.0" 104 | }, 105 | "engines": { 106 | "node": ">= 0.8" 107 | } 108 | }, 109 | "node_modules/safer-buffer": { 110 | "version": "2.1.2", 111 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 112 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 113 | }, 114 | "node_modules/setprototypeof": { 115 | "version": "1.2.0", 116 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 117 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 118 | }, 119 | "node_modules/statuses": { 120 | "version": "2.0.1", 121 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 122 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 123 | "engines": { 124 | "node": ">= 0.8" 125 | } 126 | }, 127 | "node_modules/toidentifier": { 128 | "version": "1.0.1", 129 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 130 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 131 | "engines": { 132 | "node": ">=0.6" 133 | } 134 | }, 135 | "node_modules/typescript": { 136 | "version": "5.7.2", 137 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 138 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 139 | "dev": true, 140 | "bin": { 141 | "tsc": "bin/tsc", 142 | "tsserver": "bin/tsserver" 143 | }, 144 | "engines": { 145 | "node": ">=14.17" 146 | } 147 | }, 148 | "node_modules/undici-types": { 149 | "version": "6.19.8", 150 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 151 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 152 | "dev": true 153 | }, 154 | "node_modules/unpipe": { 155 | "version": "1.0.0", 156 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 157 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 158 | "engines": { 159 | "node": ">= 0.8" 160 | } 161 | }, 162 | "node_modules/zod": { 163 | "version": "3.23.8", 164 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", 165 | "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", 166 | "funding": { 167 | "url": "https://github.com/sponsors/colinhacks" 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazon-fresh-server", 3 | "version": "0.1.0", 4 | "description": "A Model Context Protocol server", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "amazon-fresh-server": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 18 | }, 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "0.6.0" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^20.11.24", 24 | "typescript": "^5.3.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * This is a template MCP server that implements a simple notes system. 5 | * It demonstrates core MCP concepts like resources and tools by allowing: 6 | * - Listing notes as resources 7 | * - Reading individual notes 8 | * - Creating new notes via a tool 9 | * - Summarizing all notes via a prompt 10 | */ 11 | 12 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 13 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 14 | import { 15 | CallToolRequestSchema, 16 | ListResourcesRequestSchema, 17 | ListToolsRequestSchema, 18 | ReadResourceRequestSchema, 19 | ListPromptsRequestSchema, 20 | GetPromptRequestSchema, 21 | } from "@modelcontextprotocol/sdk/types.js"; 22 | import type { CallToolRequest, Tool } from "@modelcontextprotocol/sdk/types.js"; 23 | import { gzipSync } from "node:zlib"; 24 | import { Buffer } from "node:buffer"; 25 | 26 | type Unit = 27 | | "count" 28 | | "cups" 29 | | "fl_oz" 30 | | "gallons" 31 | | "grams" 32 | | "kilograms" 33 | | "liters" 34 | | "milliliters" 35 | | "ounces" 36 | | "pints" 37 | | "pounds" 38 | | "quarts" 39 | | "tbsp" 40 | | "tsp"; 41 | 42 | interface Quantity { 43 | unit: Unit; 44 | amount: number; 45 | } 46 | 47 | interface Ingredient { 48 | name: string; 49 | quantityList?: Quantity[]; 50 | brand?: string; 51 | asinOverride?: string; 52 | } 53 | 54 | interface ShoppingList { 55 | ingredients: Ingredient[]; 56 | } 57 | 58 | const createAmazonFreshLinkTool: Tool = { 59 | name: "create_amazon_fresh_link", 60 | description: `Create a link to this recipe's ingredients on Amazon Fresh. Please show the link in your response after using the tool. Here are some other suggestions on how to use this tool: 61 | For best search results, please only include the canonical ingredient in the “name” field. Best practices for the “name” field include: 62 | - Remove all unnecessary qualifiers, brands, quantities, and prep instructions (grated, sliced, chopped, crush, crumble, etc.). 63 | - Avoid packaging description such as “1-gallon box” or “1 six-pack”. We’ll find the best size container for the specified unit. 64 | - Avoid punctuation marks (periods, commas, etc.). 65 | - If you want to use brand preference, enter it in the “brand” property rather than the “name” field. Absolutely do not add a brand in both the “name” and “brand” field.`, 66 | inputSchema: { 67 | type: "object", 68 | required: ["ingredients"], 69 | properties: { 70 | ingredients: { 71 | type: "array", 72 | description: "List of ingredient objects", 73 | items: { 74 | type: "object", 75 | required: ["name"], 76 | properties: { 77 | name: { 78 | type: "string", 79 | description: 80 | "The canonical ingredient name without brand names, quantity information, or unnecessary qualifiers", 81 | }, 82 | quantityList: { 83 | type: "array", 84 | description: 85 | "List of quantity objects. If not provided, defaults to count = 1", 86 | items: { 87 | type: "object", 88 | required: ["unit", "amount"], 89 | properties: { 90 | unit: { 91 | type: "string", 92 | description: "The unit type", 93 | enum: [ 94 | "count", 95 | "cups", 96 | "fl_oz", 97 | "gallons", 98 | "grams", 99 | "kilograms", 100 | "liters", 101 | "milliliters", 102 | "ounces", 103 | "pints", 104 | "pounds", 105 | "quarts", 106 | "tbsp", 107 | "tsp", 108 | ], 109 | }, 110 | amount: { 111 | type: "number", 112 | description: "The quantity amount as an integer or float", 113 | }, 114 | }, 115 | }, 116 | }, 117 | brand: { 118 | type: "string", 119 | description: "Optional brand preference", 120 | }, 121 | asinOverride: { 122 | type: "string", 123 | description: 124 | "Optional ASIN product identifier for specific product recommendation", 125 | }, 126 | }, 127 | }, 128 | }, 129 | }, 130 | }, 131 | }; 132 | 133 | /** 134 | * Encodes a JSON object into a custom URL-safe base64 gzip-compressed format. 135 | * 136 | * @param jsonData - The JSON object to encode. 137 | * @returns The encoded string. 138 | */ 139 | function encodeToCustomFormat(jsonData: ShoppingList): string { 140 | // Convert the JSON object to a string and encode it as UTF-8 141 | const jsonString = Buffer.from(JSON.stringify(jsonData), "utf-8"); 142 | 143 | // Compress the JSON string with gzip 144 | const compressedData = gzipSync(jsonString, { level: 9 }); 145 | 146 | // Convert the compressed data to a base64 string 147 | const base64String = compressedData.toString("base64"); 148 | 149 | // Convert standard base64 to URL-safe base64 150 | return base64String 151 | .replace(/\+/g, "-") 152 | .replace(/\//g, "_") 153 | .replace(/=+$/, ""); 154 | } 155 | 156 | /** 157 | * Converts a shopping list to x-www-form-urlencoded format for Amazon Fresh 158 | * @param shoppingList The shopping list to convert 159 | * @returns The encoded URL string with the base URL and parameters 160 | */ 161 | async function convertShoppingListToFormUrl( 162 | shoppingList: ShoppingList 163 | ): Promise { 164 | const baseUrl = "https://www.amazon.com/afx/ingredients/landingencoded"; 165 | 166 | const encodedIngredients = await encodeToCustomFormat(shoppingList); 167 | 168 | return `${baseUrl}?encodedIngredients=${encodedIngredients}`; 169 | } 170 | 171 | async function main() { 172 | console.error("Starting Amazon Fresh MCP Server..."); 173 | const server = new Server( 174 | { 175 | name: "Amazon Fresh MCP Server", 176 | version: "1.0.0", 177 | }, 178 | { 179 | capabilities: { 180 | tools: {}, 181 | }, 182 | } 183 | ); 184 | 185 | server.setRequestHandler( 186 | CallToolRequestSchema, 187 | async (request: CallToolRequest) => { 188 | console.error("Received CallToolRequest:", request); 189 | try { 190 | if (!request.params.arguments) { 191 | throw new Error("No arguments provided"); 192 | } 193 | 194 | switch (request.params.name) { 195 | case "create_amazon_fresh_link": { 196 | const args = request.params.arguments as unknown as ShoppingList; 197 | const response = await convertShoppingListToFormUrl(args); 198 | return { 199 | content: [ 200 | { type: "text", text: `Here is the link: "${response}". Please render it with markdown for the user.` }, 201 | ], 202 | }; 203 | } 204 | default: 205 | throw new Error(`Unknown tool: ${request.params.name}`); 206 | } 207 | } catch (error) { 208 | console.error("Error executing tool:", error); 209 | return { 210 | content: [ 211 | { 212 | type: "text", 213 | text: JSON.stringify({ 214 | error: error instanceof Error ? error.message : String(error), 215 | }), 216 | }, 217 | ], 218 | }; 219 | } 220 | } 221 | ); 222 | 223 | server.setRequestHandler(ListToolsRequestSchema, async () => { 224 | console.error("Received ListToolsRequest"); 225 | return { 226 | tools: [createAmazonFreshLinkTool], 227 | }; 228 | }); 229 | 230 | const transport = new StdioServerTransport(); 231 | console.error("Connecting server to transport..."); 232 | await server.connect(transport); 233 | 234 | console.error("Amazon Fresh MCP Server running on stdio"); 235 | } 236 | 237 | main().catch((error) => { 238 | console.error("Fatal error in main():", error); 239 | process.exit(1); 240 | }); 241 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | --------------------------------------------------------------------------------