Skip to main content
Glama

DynamoDB Read-Only MCP

by jjikky
index.ts13.1 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { listTables, describeTable, scanTable, queryTable, paginateQueryTable, getItem, getTableAttributes, countItems, } from './dynamo-helpers'; import { QueryCommandInput } from '@aws-sdk/lib-dynamodb'; const server = new McpServer({ name: 'dynamo-readonly-mcp', version: '0.0.1', }); server.tool('list-tables', 'Get a list of all DynamoDB tables', {}, async () => { try { console.error('# list-tables tool has been called'); const tables = await listTables(); return { content: [ { type: 'text', text: JSON.stringify(tables, null, 2), }, ], }; } catch (error: any) { return { isError: true, content: [ { type: 'text', text: `Error occurred: ${error.message}`, }, ], }; } }); server.tool( 'describe-table', 'Get detailed information about a DynamoDB table', { tableName: z.string().describe('Name of the table to get details for'), }, async ({ tableName }) => { try { const tableInfo = await describeTable(tableName); return { content: [ { type: 'text', text: JSON.stringify(tableInfo, null, 2), }, ], }; } catch (error: any) { return { isError: true, content: [ { type: 'text', text: `Error occurred: ${error.message}`, }, ], }; } } ); server.tool( 'scan-table', 'Scan items from a DynamoDB table', { tableName: z.string().describe('Name of the table to scan'), limit: z.number().optional().describe('Maximum number of items to return (default: 20)'), filterExpression: z.string().optional().describe("Filter expression (e.g: 'age > :minAge')"), expressionAttributeValues: z .record(z.any()) .optional() .describe('Filter expression attribute values (JSON format)'), projectionExpression: z.string().optional().describe('Projection expression (e.g: "id")'), }, async ({ tableName, limit, filterExpression, expressionAttributeValues, projectionExpression, }) => { try { const items = await scanTable( tableName, limit, filterExpression, expressionAttributeValues, projectionExpression ); return { content: [ { type: 'text', text: JSON.stringify(items, null, 2), }, ], }; } catch (error: any) { return { isError: true, content: [ { type: 'text', text: `Error occurred: ${error.message}`, }, ], }; } } ); server.tool( 'query-table', 'Query items from a DynamoDB table based on conditions', { tableName: z.string().describe('Name of the table to query'), keyConditionExpression: z.string().describe("Key condition expression (e.g: 'PK = :pk')"), expressionAttributeValues: z .record(z.any()) .describe('Filter expression attribute values (JSON format)'), indexName: z.string().optional().describe('Name of the index to use (optional)'), filterExpression: z.string().optional().describe('Filter expression (optional)'), limit: z.number().optional().describe('Maximum number of items to return'), projectionExpression: z.string().optional().describe('Projection expression (optional)'), }, async ({ tableName, keyConditionExpression, expressionAttributeValues, indexName, filterExpression, limit, projectionExpression, }) => { console.error('# query-table tool has been called'); try { const queryParams: QueryCommandInput = { TableName: tableName, KeyConditionExpression: keyConditionExpression, ExpressionAttributeValues: expressionAttributeValues, ProjectionExpression: projectionExpression, }; if (indexName) { queryParams.IndexName = indexName; } if (filterExpression) { queryParams.FilterExpression = filterExpression; } if (limit) { queryParams.Limit = limit; } if (projectionExpression) { queryParams.ProjectionExpression = projectionExpression; } const items = await queryTable(queryParams); return { content: [ { type: 'text', text: JSON.stringify(items, null, 2), }, ], }; } catch (error: any) { console.error('# Error executing query:', error); return { isError: true, content: [ { type: 'text', text: `Error occurred: ${error.message}`, }, ], }; } } ); server.tool( 'paginate-query-table', 'Paginate query results', { tableName: z.string().describe('Table name'), keyConditionExpression: z.string().describe("Key condition expression (e.g: 'PK = :pk')"), expressionAttributeValues: z .record(z.any()) .describe('Filter expression attribute values (JSON format)'), projectionExpression: z.string().optional().describe('Projection expression (optional)'), }, async ({ tableName, keyConditionExpression, expressionAttributeValues, projectionExpression, }) => { try { const queryParams: QueryCommandInput = { TableName: tableName, KeyConditionExpression: keyConditionExpression, ExpressionAttributeValues: expressionAttributeValues, }; if (projectionExpression) { queryParams.ProjectionExpression = projectionExpression; } const paginatedItems = await paginateQueryTable(queryParams); return { content: [ { type: 'text', text: JSON.stringify(paginatedItems, null, 2), }, ], }; } catch (error: any) { return { isError: true, content: [ { type: 'text', text: `Error occurred: ${error.message}`, }, ], }; } } ); server.tool( 'get-item', 'Get an item from a DynamoDB table based on a specific key', { tableName: z.string().describe('Table name'), key: z.record(z.any()).describe('Item key (JSON format)'), }, async ({ tableName, key }) => { try { const item = await getItem(tableName, key); if (!item) { return { content: [ { type: 'text', text: 'Could not find the corresponding item.', }, ], }; } return { content: [ { type: 'text', text: JSON.stringify(item, null, 2), }, ], }; } catch (error: any) { return { isError: true, content: [ { type: 'text', text: `Error occurred: ${error.message}`, }, ], }; } } ); server.tool( 'count-items', 'Count items in a DynamoDB table', { tableName: z.string().describe('Table name'), filterExpression: z.string().optional().describe('Filter expression (optional)'), expressionAttributeValues: z .record(z.any()) .optional() .describe('Filter expression attribute values (optional)'), }, async ({ tableName, filterExpression, expressionAttributeValues }) => { try { const count = await countItems(tableName, filterExpression, expressionAttributeValues); return { content: [ { type: 'text', text: `Table "${tableName}" has ${count} items.`, }, ], }; } catch (error: any) { return { isError: true, content: [ { type: 'text', text: `Error occurred: ${error.message}`, }, ], }; } } ); server.resource('dynamodb-tables-info', 'DynamoDB table information', async () => { try { const tables = await listTables(); const tablesInfo = await Promise.all( tables.map(async (tableName: string) => { try { return await describeTable(tableName); } catch (error) { return { TableName: tableName, Error: 'Could not get table information.' }; } }) ); return { contents: [ { uri: 'dynamodb://tables-info', text: JSON.stringify(tablesInfo, null, 2), mimeType: 'application/json', }, ], }; } catch (error: any) { return { contents: [ { uri: 'dynamodb://tables-info', text: JSON.stringify({ error: error.message }, null, 2), mimeType: 'application/json', }, ], }; } }); server.resource('dynamodb-table-schema', 'DynamoDB table schema information', async (request) => { try { const pathParts = request.pathname?.split('/') || []; const tableName = pathParts[pathParts.length - 1]; if (!tableName) { return { contents: [ { uri: request.toString(), text: JSON.stringify({ error: 'Table name was not provided.' }, null, 2), mimeType: 'application/json', }, ], }; } const attributes = await getTableAttributes(tableName); return { contents: [ { uri: request.toString(), text: JSON.stringify(attributes, null, 2), mimeType: 'application/json', }, ], }; } catch (error: any) { return { contents: [ { uri: request.toString(), text: JSON.stringify({ error: error.message }, null, 2), mimeType: 'application/json', }, ], }; } }); server.prompt( 'dynamodb-query-help', 'Prompt to help you write a DynamoDB query', { tableName: z.string().describe('Name of the table to query'), queryType: z.string().optional().describe('Query complexity level ("basic" or "advanced")'), }, async ({ tableName, queryType = 'basic' }) => { let helpContent = ''; try { const tableInfo = await describeTable(tableName); if (queryType === 'basic') { helpContent = ` # Basic query guide for table ${tableName} ## Table structure ${JSON.stringify(tableInfo?.KeySchema, null, 2)} ## Query example \`\`\`json { "TableName": "${tableName}", "KeyConditionExpression": "partitionKeyName = :partitionValue", "ExpressionAttributeValues": { ":partitionValue": "desired value" } } \`\`\` ## Basic operations 1. Scan: Use 'scan-table' tool to scan the entire table. 2. Single item retrieval: Use 'get-item' tool. 3. Table information retrieval: Use 'describe-table' tool. 4. Item count calculation: Use 'count-items' tool. `; } else { helpContent = ` # Advanced query guide for table ${tableName} ## Table structure ${JSON.stringify(tableInfo, null, 2)} ## Advanced query example \`\`\`json { "TableName": "${tableName}", "KeyConditionExpression": "partitionKeyName = :partitionValue AND sortKeyName BETWEEN :low AND :high", "FilterExpression": "attributeName = :attrValue", "ExpressionAttributeValues": { ":partitionValue": "desired value", ":low": "minimum value", ":high": "maximum value", ":attrValue": "filter value" } } \`\`\` ## Advanced expressions 1. BEGINS_WITH: "attributeName BEGINS_WITH :prefix" 2. CONTAINS: "attributeName CONTAINS :substring" 3. Comparison operators: =, <>, <, <=, >, >= 4. Logical operators: AND, OR, NOT ## Query using indexes ${ tableInfo?.GlobalSecondaryIndexes && tableInfo.GlobalSecondaryIndexes.length > 0 ? `This table has the following GSIs:\n${tableInfo.GlobalSecondaryIndexes.map( (idx: any) => `- ${idx.IndexName}` ).join('\n')}` : 'This table has no GSI.' } `; } } catch (error: any) { helpContent = ` # DynamoDB query help An error occurred while getting detailed information about table "${tableName}": ${error.message} ## General DynamoDB operations 1. Use 'list-tables' tool to get table list 2. Use 'describe-table' tool to view table details 3. Use 'scan-table' tool to scan 4. Use 'query-table' tool to query 5. Use 'paginate-query-table' tool to paginate query 6. Use 'get-item' tool to retrieve an item 7. Use 'count-items' tool to count items First, check table information and try again. `; } return { messages: [ { role: 'user', content: { type: 'text', text: helpContent, }, }, ], }; } ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('# DynamoDB read-only MCP server is running...'); } main().catch((error) => { console.error('# Server execution error:', error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jjikky/dynamo-readonly-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server