Etsy MCP Server
by georgejeffers
An MCP server for interacting with the Etsy API. This service handles authentication, API calls to Etsy for managing listings, shop details, shipping profiles, and image uploads.
Last updated: N/A
Etsy MCP Server
An MCP (Model Context Protocol) server for interacting with the Etsy API. This service handles authentication, API calls to Etsy for managing listings, shop details, shipping profiles, and image uploads.
Overview
This service provides a set of tools accessible via the Model Context Protocol (MCP) to interact with the Etsy V3 API. It manages the OAuth 2.0 authentication flow (including PKCE token refresh), stores tokens securely, and exposes functionalities like creating listings, fetching shop data, managing shipping profiles, and uploading images. The goal is to simplify interactions with the Etsy API for MCP-compatible clients.
This MCP server is primarily designed for development and tool integration. Please ensure you comply with Etsy's API Terms of Use.
Features
- đ OAuth2 Authentication: Handles the complete Etsy OAuth2 flow with PKCE.
- đī¸ Listing Management: Create, and retrieve Etsy listings.
- âšī¸ Shop Information: Fetch details for your Etsy shop.
- đĻ Shipping Profiles: List and create shipping profiles for your shop.
- đŧī¸ Image Uploads: Upload images and associate them with your listings.
- đ§ Default Shop Management: Set and get a default shop for easier multi-shop use.
- đ Secure Token Storage: Persists OAuth tokens securely.
Installation
Prerequisites
- Node.js (version 18.0.0 or higher recommended - see
package.json
engines) - npm (usually comes with Node.js) or yarn
Manual Installation
-
Clone the repository (replace with your repository URL if you host it on GitHub/GitLab etc.):
git clone <your-repository-url> cd etsy-mcp
(If you're working locally, you can skip the clone and just
cd
into the project directory.) -
Install dependencies:
npm install
Or if you use yarn:
yarn install
-
Configure Environment Variables: Copy the
.env.example
file to a new file named.env
:cp .env.example .env
Then, open
.env
and fill in your Etsy App credentials:ETSY_API_KEY=YOUR_ETSY_APP_API_KEY ETSY_CLIENT_SECRET=YOUR_ETSY_APP_CLIENT_SECRET # Optional: define a specific host and port for the OAuth callback redirect URI # ETSY_MCP_HOST=localhost # ETSY_MCP_OAUTH_PORT=3003 # ETSY_MCP_REDIRECT_URI=http://localhost:3003/oauth/callback # Optional: define a custom path for token storage and logs # ETSY_MCP_TOKEN_PATH=/path/to/your/token_storage_directory # ETSY_MCP_LOG_PATH=/path/to/your/logs_directory # Optional: Your default shipping profile ID for physical items # SHIPPING_PROFILE_ID=YOUR_DEFAULT_SHIPPING_PROFILE_ID
Note: The
REDIRECT_URI
in your.env
file (or the defaulthttp://localhost:3003/oauth/callback
) must match one of the redirect URIs configured for your Etsy App.
Usage
Run the Service
- For production/built version:
npm run build npm start
- For development with auto-reload:
npm run dev
This will launch the server, which listens for MCP commands, typically via standard input/output.
Use in Cursor IDE (or other MCP Clients)
To use this Etsy MCP server with an MCP client like Cursor IDE, you'll typically provide a JSON configuration.
In Cursor settings, navigate to the MCP tab, click "+ Add new global MCP server", and input:
{
"mcpServers": {
"Etsy MCP": {
"command": "npm",
"args": ["run", "dev"],
"env": {
"ETSY_API_KEY": "YOUR_ETSY_API_KEY_HERE",
"ETSY_CLIENT_SECRET": "YOUR_ETSY_CLIENT_SECRET_HERE"
// Add other ENV VARS from your .env if needed, e.g., REDIRECT_URI
// "REDIRECT_URI": "http://localhost:3003/oauth/callback"
// "SHIPPING_PROFILE_ID": "YOUR_DEFAULT_SHIPPING_PROFILE_ID_HERE"
},
"cwd": "/Users/name/Desktop/repos/ai gemini apps/etsy_api/etsy-mcp" // IMPORTANT: Update this to the correct absolute path of your project
}
}
}
Key points for the client configuration:
command
&args
: The example usesnpm run dev
for development. For a production setup, you might usenpm start
(which impliesnpm run build
first if not already built).env
:- Replace
YOUR_ETSY_API_KEY_HERE
andYOUR_ETSY_CLIENT_SECRET_HERE
with your actual credentials if you don't want to rely on the.env
file being picked up by thenpm run dev
script's environment. It's generally better to let the server pick up credentials from its.env
file. The example above is for overriding or ensuring they are set for the client's execution context.
- Replace
cwd
: Crucially, update this to the absolute path where youretsy-mcp
project is located on your machine.- The
tools
array can also be defined here, but the server itself reports its capabilities, so it's often not strictly needed in the client-side static configuration if the client can dynamically fetch them.
API Reference
The service provides the following MCP tools. Parameters are based on Zod schemas defined in the server.
authenticate()
Initiates the OAuth2 authentication flow with Etsy.
- Description: Starts the authentication process. The server will return a URL that you need to open in a browser to grant access to your Etsy account.
- Parameters: None.
- Returns:
{
"content": [
{
"type": "text",
"text": "Please visit this URL to authorize the application: https://www.etsy.com/oauth/connect?response_type=code&client_id=YOUR_API_KEY...\nComplete the process in your browser. You can then use other tools."
}
]
}
set_default_shop()
Fetches your Etsy shops and sets the first one found as the default for this session.
- Description: After successful authentication, use this tool to select a default shop. If you have multiple shops, it will automatically use the first one returned by Etsy. This default shop will be used by other tools unless a
shop_id
is explicitly provided to them. - Parameters: None.
- Returns:
{
"content": [
{
"type": "text",
"text": "Default shop set to: YourShopName (ID: 12345678). Other tools will now use this shop unless a specific shop_id is provided."
}
]
}
Or if no shops are found:
{
"content": [
{
"type": "text",
"text": "No shops found for your Etsy account."
}
]
}
get_default_shop()
Gets the currently configured default Etsy shop ID and name.
- Description: Use this to check which shop is currently set as the default.
- Parameters: None.
- Returns:
{
"content": [
{
"type": "text",
"text": "Current default shop: YourShopName (ID: 12345678)"
}
]
}
Or if no default shop is set:
{
"content": [
{
"type": "text",
"text": "No default shop is currently set. Please run the `set_default_shop` tool after authenticating."
}
]
}
get_listings(shop_id?: number)
Get all active listings for a shop.
- Parameters:
shop_id
(number, optional): The ID of the shop. If not provided, uses the default shop.
- Returns:
{
"content": [
{
"type": "text",
"text": "{\n \"count\": 1,\n \"results\": [\n {\n \"listing_id\": 123456789,\n \"user_id\": 98765432,\n \"shop_id\": 12345678,\n \"title\": \"Handmade Ceramic Mug\",\n \"description\": \"A beautiful handmade ceramic mug...\",\n \"state\": \"active\",\n \"price\": { \"amount\": 2500, \"divisor\": 100, \"currency_code\": \"USD\" },\n \"quantity\": 5,\n /* ... other listing fields ... */\n }\n ]\n}"
}
]
}
get_shop_details(shop_id?: number)
Get details for a specific shop.
- Parameters:
shop_id
(number, optional): The ID of the shop. If not provided, uses the default shop.
- Returns:
{
"content": [
{
"type": "text",
"text": "{\n \"shop_id\": 12345678,\n \"shop_name\": \"YourShopName\",\n \"user_id\": 98765432,\n \"title\": \"Unique Handmade Goods\",\n \"status\": \"active\",\n /* ... other shop fields ... */\n}"
}
]
}
create_listing(shop_id?: number, listing_data: object)
Creates a new Etsy listing.
- Description: For physical items, ensure you have a
shipping_profile_id
. To add images, create the listing first, then use theupload_listing_image
tool with the returnedlisting_id
. - Parameters:
shop_id
(number, optional): The ID of the shop. If not provided, uses the default shop.listing_data
(object):title
(string): Listing title.description
(string): Listing description.price
(number): Listing price (e.g., 25.99).quantity
(number): Available quantity.who_made
(string): e.g., 'i_did', 'collective', 'someone_else'.when_made
(string): e.g., 'made_to_order', '2020_2024', '1950_1959'.taxonomy_id
(number): The numeric ID of the listing's category.shipping_profile_id
(number, optional): The numeric ID of the shipping profile. Required iftype
is 'physical'.type
(enum: "physical", "digital", "download"): Listing type.
- Returns:
{
"content": [
{
"type": "text",
"text": "{\n \"listing_id\": 123456790,\n \"user_id\": 98765432,\n \"shop_id\": 12345678,\n \"title\": \"New Awesome Product\",\n /* ... other fields of the newly created listing ... */\n}"
}
]
}
list_shop_shipping_profiles(shop_id?: number)
Lists all shipping profiles for a given shop.
- Parameters:
shop_id
(number, optional): The ID of the shop. If not provided, uses the default shop.
- Returns:
{
"content": [
{
"type": "text",
"text": "{\n \"count\": 1,\n \"results\": [\n {\n \"shipping_profile_id\": 7654321,\n \"title\": \"Standard US Shipping\",\n \"origin_country_iso\": \"US\",\n /* ... other shipping profile fields ... */\n }\n ]\n}"
}
]
}
create_shop_shipping_profile(shop_id?: number, title: string, origin_country_iso: string, primary_cost: number, secondary_cost: number, min_processing_time: number, max_processing_time: number, destination_country_iso?: string, destination_region?: string)
Creates a new shipping profile for a shop.
- Parameters:
shop_id
(number, optional): The ID of the shop. If not provided, uses the default shop.title
(string): A title for the shipping profile (e.g., 'US Standard').origin_country_iso
(string, length 2): ISO code of the origin country (e.g., 'US').primary_cost
(number, min 0): Cost of shipping to this destination alone.secondary_cost
(number, min 0): Cost of shipping with another item.min_processing_time
(integer, min 1): Minimum days to process the order.max_processing_time
(integer, min 1): Maximum days to process the order.destination_country_iso
(string, length 2, optional): ISO code for a specific destination country.destination_region
(enum: "eu", "non_eu", "none", optional): A specific destination region. (Note: Eitherdestination_country_iso
ORdestination_region
must be provided, but not both.)
- Returns:
{
"content": [
{
"type": "text",
"text": "{\n \"shipping_profile_id\": 7654322,\n \"title\": \"New Express Profile\",\n /* ... other fields of the newly created shipping profile ... */\n}"
}
]
}
upload_listing_image(shop_id?: number, listing_id: number, file_name: string, image_name?: string)
Uploads an image from a predefined local server directory and associates it with an Etsy listing.
- Description: After creating a listing, use this tool to upload images. Images must be placed in the
public/uploads/listing_images
directory on the server (relative to the project root). - Parameters:
shop_id
(number, optional): Shop ID. Uses default if not provided.listing_id
(integer): The ID of the listing to add the image to.file_name
(string): The name of the image file (e.g., 'my_image.jpg') located in the server'spublic/uploads/listing_images
directory.image_name
(string, optional): The desired filename for the image on Etsy. Defaults tofile_name
if not provided.
- Returns:
{
"content": [
{
"type": "text",
"text": "{\n \"listing_image_id\": 987654321,\n \"listing_id\": 123456790,\n \"hex_code\": \"f1e2d3\",\n \"url_75x75\": \"https://.../75x75.jpg\",\n /* ... other image details ... */\n}"
}
]
}
How It Works
- Client Request: An MCP client sends a tool call request to this server (e.g., via
stdio
when run locally). - Authentication Check: For tools requiring authentication, the server checks for a valid OAuth token using
TokenStorage
. - OAuth Flow (if needed):
a. If no valid token exists, the
authenticate
tool is typically invoked by the client (or instructed by the server). b. TheEtsyMCPServer
calls theOAuthServer
to generate an Etsy authorization URL (using PKCE). c. The user opens this URL in a browser and authorizes the application. d. Etsy redirects to theOAuthServer
's callback endpoint with an authorization code. e. TheOAuthServer
exchanges the code for an access token and refresh token using the PKCE code verifier. f. Tokens (includinguser_id
and potentially defaultshop_id
andshop_name
) are saved byTokenStorage
. - API Call: The
EtsyMCPServer
uses theEtsyApiClient
(which holds the access token) to make the requested API call to the Etsy V3 API. - Token Refresh: If an access token is expired,
getValidAccessToken()
inEtsyMCPServer
attempts to use the refresh token to get a new access token. - Response: The response from the Etsy API is processed and returned to the MCP client in the standard MCP format.
Troubleshooting
- Authentication Errors / Invalid Grant:
- Ensure your
ETSY_API_KEY
,ETSY_CLIENT_SECRET
in.env
are correct. - Verify that the
REDIRECT_URI
used by the server (defaulthttp://localhost:3003/oauth/callback
or as set in.env
) exactly matches one of the "Callback URLs" configured in your Etsy App settings. - Stale tokens: Try clearing
tokens.json
from your token storage path (seeTokenStorage.ts
or yourETSY_MCP_TOKEN_PATH
env var) and re-authenticating.
- Ensure your
EADDRINUSE
for OAuth Server: This means the port (default 3003) for the OAuth callback server is already in use. Stop the other process or configure a differentETSY_MCP_OAUTH_PORT
in your.env
file and update your Etsy App's redirect URI accordingly.- File Not Found for Image Upload: Ensure the
file_name
provided toupload_listing_image
exists within thepublic/uploads/listing_images/
directory relative to your project root. - Log Files: Check the log files generated in the
logs
directory (orETSY_MCP_LOG_PATH
) for detailed error messages. Each run creates a timestamped log file.
License
MIT License (Please create a LICENSE file with the MIT License text if you choose this license, or specify another.)