Using Claude Agent SDK with DataDoe MCP
This tutorial shows how to build a small TypeScript app with @anthropic-ai/claude-agent-sdk and connect it to DataDoe MCP. The app asks DataDoe for sales data, lets Claude analyze it, and writes a structured JSON report to ./response/.
Every file is listed in full, so you can build the project by copying the blocks below in order.
What you will build
By the end, you will have a small agent that:
- connects to DataDoe MCP over HTTP from the Claude Agent SDK
- registers a local tool for the final handoff
- asks DataDoe for export data, then analyzes it in the agent
- writes the final result as JSON on disk
Prerequisites
- Completion of the DataDoe MCP Overview setup, with an MCP key
- An Anthropic API key (opens in a new tab)
- Node.js 24 or later, which runs TypeScript directly with no build step
The SDK bundles the Claude Code binary as an optional dependency, so there is nothing else to install. If you install with
--omit=optional, install Claude Code separately and point the SDK at it withpathToClaudeCodeExecutable.
Step 1: Set up the project
1mkdir datadoe-claude-agent && cd datadoe-claude-agent
2npm init -y
3npm pkg set type=module
4npm install @anthropic-ai/claude-agent-sdk zod
5mkdir srcSetting type=module matters. Without it, Node refuses the files below with SyntaxError: Cannot use import statement outside a module.
Step 2: Configure your keys
Create a .env file in the project root:
1DATADOE_MCP_KEY=YOUR_DATADOE_MCP_KEY
2ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEYDATADOE_MCP_KEY: your MCP key from DataDoe MCP Integrations (opens in a new tab)ANTHROPIC_API_KEY: your Anthropic API key
Node loads this file with --env-file when you run the app, so no extra dependency is needed. Keep .env out of version control.
Step 3: Create the output tool
The agent needs one local tool for the final handoff, so the result lands on disk in a predictable shape instead of being buried in chat text.
Create src/tools.ts:
1import { mkdir, writeFile } from 'node:fs/promises';
2import { z } from 'zod';
3
4import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
5
6export const localServer = createSdkMcpServer({
7 name: 'local',
8 version: '1.0.0',
9 tools: [
10 tool(
11 'output_top_asins',
12 'Write the final ranked list of top ASINs to a timestamped JSON file in ./response. Call this once, at the end, with the complete list.',
13 {
14 asins: z
15 .array(
16 z.object({
17 asin: z.string().describe('The ASIN, for example B08N5WRWNW'),
18 title: z.string().describe('Product title'),
19 unitsSold: z.number().describe('Units sold in the period')
20 })
21 )
22 .min(1)
23 .describe('Ranked best first')
24 },
25 async ({ asins }) => {
26 await mkdir('response', { recursive: true });
27 const filePath = `response/top-asins-${Date.now()}.json`;
28 await writeFile(filePath, JSON.stringify(asins, null, 2), 'utf8');
29
30 return {
31 content: [{ type: 'text', text: `Wrote ${asins.length} ASINs to ${filePath}` }]
32 };
33 }
34 )
35 ]
36});Two details that are easy to get wrong: the input schema is a raw Zod shape (a plain object of fields), not a wrapped z.object({...}), and the handler returns a content array rather than a bare value.
Step 4: Write the system prompt
The system prompt turns a general assistant into a focused workflow. Create src/prompt.ts:
1export const systemPrompt = `You are an expert Amazon marketplace data analyst with access to DataDoe MCP, which provides live Amazon Seller and Vendor data.
2
3## Data access pattern
4
5Use the DataDoe export tools to fetch sales data - \`exports_raw_download\` returns the file contents directly - then analyze them inline. No local SQL engine is needed.
6
7## Output rule
8
9After completing your analysis you MUST call \`mcp__local__output_top_asins\` exactly once with all results. Never write the final JSON directly in your response text - always route it through that tool.
10`;Step 5: Create the agent
Create src/index.ts. This is the complete entry point:
1import { query } from '@anthropic-ai/claude-agent-sdk';
2
3import { systemPrompt } from './prompt.ts';
4import { localServer } from './tools.ts';
5
6const mcpKey = process.env.DATADOE_MCP_KEY;
7
8if (!mcpKey) {
9 throw new Error('DATADOE_MCP_KEY is not set. Add it to your .env file.');
10}
11
12const response = query({
13 prompt: 'Analyze my Amazon sales for the last 7 days and find the top 3 ASINs by units sold.',
14 options: {
15 model: 'sonnet',
16 systemPrompt,
17 mcpServers: {
18 datadoe: {
19 type: 'http',
20 url: 'https://mcp.datadoe.com/mcp/v1',
21 headers: { 'datadoe-mcp-key': mcpKey }
22 },
23 local: localServer
24 },
25 allowedTools: ['mcp__datadoe__*', 'mcp__local__output_top_asins'],
26 permissionMode: 'dontAsk',
27 tools: [],
28 settingSources: [],
29 maxTurns: 20
30 }
31});
32
33try {
34 for await (const message of response) {
35 if (message.type === 'system' && message.subtype === 'init') {
36 console.log('MCP servers:', message.mcp_servers);
37 }
38
39 if (message.type === 'result') {
40 if (message.subtype === 'success') {
41 console.log(message.result);
42 } else {
43 console.error('Run failed:', message.subtype);
44 process.exitCode = 1;
45 }
46 }
47 }
48} catch (error) {
49 console.error('Agent run failed:', error instanceof Error ? error.message : error);
50 process.exitCode = 1;
51}Note the local import paths end in .ts. Node requires the explicit extension when it runs TypeScript directly.
Why these options
Each option in the block above earns its place. Leaving one out causes a specific problem:
allowedTools: MCP tools are unusable without an allow rule. The entries must be anchored (mcp__datadoe__*works; a bare*is ignored).permissionMode: 'dontAsk': runs the listed tools and denies everything else outright, instead of pausing for a human. This is what an unattended script needs. AvoidbypassPermissionshere: it is broader than necessary andallowedToolsno longer constrains it.tools: []: removes Claude's built-in tools, so the agent works only through your MCP servers.settingSources: []: stops the SDK from loading your personal~/.claudeconfiguration, which would otherwise pull unrelated MCP servers into the run.maxTurns: a safety stop so a confused run cannot loop forever.
Step 6: Run the app
1node --env-file=.env src/index.tsOn startup the app prints the MCP server status. Both should be ready:
MCP servers: [ { name: 'datadoe', status: 'connected' }, { name: 'local', status: 'connected' } ]If datadoe shows needs-auth, the MCP key is missing or wrong. The run then:
- asks DataDoe MCP for the required sales export
- lets Claude identify the top 3 ASINs by units sold
- calls
output_top_asinsonce - writes the result to
./response/top-asins-<timestamp>.json
A simple mental model
To reuse this pattern in another app, keep the split the same:
- DataDoe MCP for Amazon data access
- Claude Agent SDK for the agent loop and tool orchestration
- local tools for file output and any small app-specific helpers
That separation keeps the app easy to extend. You do not need to turn the agent into a pile of prompt instructions.
Related resources
- DataDoe MCP Overview
- DataDoe MCP Integrations (opens in a new tab)
- Claude Agent SDK documentation (opens in a new tab)
- Custom tools in the Claude Agent SDK (opens in a new tab)
DataDoe MCP resources
Check the following resources for more information:
- MCP server URL:
https://mcp.datadoe.com/mcp/v1 - Interactive Data Scheme
- Data Scheme JSON: https://api.datadoe.com/api/v1/spec/data-scheme
- Need help? Use the contact form

