developmentTechArticle

Building a DynamoDB MCP Server

Read items from DynamoDB with GetCommand and QueryCommand as MCP tools, and understand why Scan should stay out of the tool surface.

Quick Answer / TL;DR

A DynamoDB MCP server should expose GetItem and Query, which use indexes and stay fast and cheap at any table size, while leaving out Scan, which reads the entire table and is the easiest way for a model-generated request to become slow and expensive.

Key Takeaways

  • Query requires a partition key and uses an index; Scan reads every item in the table regardless of filters applied afterward - the cost difference at scale is enormous.
  • DynamoDB bills read/write capacity separately from storage, so an unbounded Scan tool is a direct, uncapped cost risk in a way a Query tool isn't.
  • The DynamoDBDocumentClient wrapper handles marshalling JS types to DynamoDB's attribute-value format automatically, which the lower-level DynamoDBClient does not.

Why not just copy a reference implementation?

The official MCP servers repository is explicit about this: "The servers in this repository are intended as reference implementations to demonstrate MCP features and SDK usage... not as production-ready solutions." The AWS SDK makes ScanCommand exactly as easy to call as QueryCommand, so a tool built by following the SDK docs alone has no reason to leave it out - it's an explicit choice made below, not a default.

Get and Query, deliberately without Scan

DynamoDBDocumentClient.from() wraps the base client so tool code works with plain JS objects instead of DynamoDB's { S: "value" }-style attribute maps. Only GetCommand and QueryCommand are wired into tools here - ScanCommand exists in the SDK but is intentionally left unused.

typescript
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand, QueryCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: "ap-south-1" });
const docClient = DynamoDBDocumentClient.from(client);

async function getItem(tableName: string, key: Record<string, string>) {
  const result = await docClient.send(new GetCommand({ TableName: tableName, Key: key }));
  return result.Item ?? null;
}

async function queryItems(tableName: string, partitionKey: string, partitionValue: string, limit = 50) {
  const result = await docClient.send(new QueryCommand({
    TableName: tableName,
    KeyConditionExpression: "#pk = :pkval",
    ExpressionAttributeNames: { "#pk": partitionKey },
    ExpressionAttributeValues: { ":pkval": partitionValue },
    Limit: limit,
  }));
  return result.Items ?? [];
}

Building a DynamoDB MCP Server FAQs

Direct answers for developers, operators, and Indian teams evaluating MCP.

M
MCPserver Team

MCP documentation and protocol implementation team

Published: 2026-07-21
Updated: 2026-07-21