Skip to main content
Glama
satyamsh04

custom-mcp-server

by satyamsh04

custom-mcp-server

A production Model Context Protocol server for a data-annotation workflow. It exposes six tools over the MCP stdio transport, backed by AWS S3 + DynamoDB and Slack, with JWT auth, per-tool rate limiting, and exponential-backoff retries.

Prerequisites

  • Node.js 20 LTS

  • npm

  • AWS account (S3 bucket + DynamoDB table) and a Slack bot token for runtime use (not required to run the test suite — all external calls are mocked)

Related MCP server: Slack MCP Server

Install

npm install

Environment setup

Copy .env.example to .env and fill in the values. Keys:

Key

Required by

Notes

AWS_REGION

all AWS tools

e.g. us-east-1

AWS_ACCESS_KEY_ID

all AWS tools

secret — keep out of source control

AWS_SECRET_ACCESS_KEY

all AWS tools

secret

S3_BUCKET_NAME

s3_upload, s3_download

default bucket

DYNAMO_TABLE_NAME

dynamo_read/write, annotation_status

table with partition key id

SLACK_BOT_TOKEN

slack_notify, annotation_status

secret, xoxb-...

SLACK_DEFAULT_CHANNEL

slack_notify, annotation_status

e.g. #annotations

OAUTH_ISSUER

auth (every call)

expected iss claim

OAUTH_AUDIENCE

auth (every call)

expected aud claim

JWKS_URI

auth (RS256)

JWKS endpoint for signature verification

JWT_SECRET

auth (HS256, dev only)

optional; ≥ 32 chars; refused when NODE_ENV=production

NODE_ENV

auth

set to production to force RS256/JWKS and forbid HS256

RATE_LIMIT_PER_MIN

rate limiter

default 100

RETRY_MAX_ATTEMPTS

retry

default 3

RETRY_BASE_DELAY_MS

retry

default 200

LOG_LEVEL

logger

debug/info/warn/error, default info

Build / test / run

npm run build       # compile TypeScript to dist/
npm run typecheck   # tsc --noEmit
npm test            # Jest (ESM) — all external calls mocked
npm start           # node dist/server.js (stdio transport)

Tools

Tool

Input (required**)

Required scope

Behavior

s3_upload

key, contentBase64, contentType?

s3:write

Upload base64 content to S3 under the caller's prefix; returns { bucket, key, etag }

s3_download

key**

s3:read

Download object from the caller's prefix; returns { bucket, key, contentBase64, contentType }

dynamo_read

id**, consistentRead?

dynamo:read

Read a record the caller owns; returns the item (without owner) or { found: false }

dynamo_write

id, attributes, overwrite?

dynamo:write

Put a record stamped with the caller as owner; can only overwrite records the caller owns

slack_notify

message**, channel?, threadTs?

slack:write

Post to Slack (text sanitized); returns { channel, ts }

annotation_status

taskId**, newStatus?, notify?

annotation:read (+ annotation:write to update)

Read/update a task the caller owns, optionally notify Slack

Auth model

Every tool call is authenticated and authorized:

  • Authentication. The caller supplies a JWT via _meta.authorization (optionally Bearer-prefixed). The expected algorithm is pinned from server configuration — not the token header — to block algorithm-confusion attacks: RS256 (verified against JWKS_URI) by default, or HS256 only when a JWT_SECRET (≥ 32 chars) is set and NODE_ENV is not production. The server checks iss/aud/expiry (with a small clock skew) and derives an AuthContext (subject, scopes). Invalid tokens → AUTH_INVALID.

  • Scope authorization. Each tool declares requiredScopes. A token missing a required scope is rejected with FORBIDDEN before the handler runs.

  • Object-level authorization (ownership). DynamoDB records carry an owner attribute and S3 keys are confined to a per-subject prefix (<subject>/…). Callers can only read/update their own records and objects; foreign records are reported as not-found to avoid ID enumeration. This prevents IDOR.

  • Error handling. Callers receive only a stable error code plus a requestId; full error detail is logged server-side (stderr) and never leaked to the client.

JWKS keys are fetched through a cached, rate-limited client to avoid a network round-trip (and IdP DoS) on every verification. S3 up/downloads are capped at 10 MiB to bound memory use.

Rate limiting & retries

  • Rate limit: 100 requests/min per principal+tool (configurable), in-memory per process, keyed by subject:tool so one caller cannot starve others. Unknown tool names are rejected before consuming limiter budget. Exceeding it yields RATE_LIMITED.

  • Retry: transient failures (retryable: true) are retried up to 3 times with exponential backoff (baseDelay * 2^(n-1)). Conflicts, validation, and auth errors are never retried.

  • Idempotency note: retries wrap non-idempotent writes (dynamo_write, slack_notify). Only transient errors are retried, but adding idempotency keys is recommended future work.

Cursor setup

.cursor/mcp.json registers the server with Cursor:

{
  "mcpServers": {
    "custom-mcp-server": {
      "command": "node",
      "args": ["dist/server.js"],
      "env": { "AWS_REGION": "us-east-1", "...": "..." }
    }
  }
}

Secrets (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, SLACK_BOT_TOKEN, JWT_SECRET) are not placed in mcp.json; provide them via your shell environment / .env. Run npm run build before launching so dist/server.js exists.

Architecture

See PLAN.md for the full milestone plan, interface contracts, and blocker analysis. Source layout:

src/
  server.ts            stdio transport + tool-call pipeline
  config.ts            env loading + validation (zod)
  types.ts             shared interface contracts
  errors.ts            AppError exception + guards
  security.ts          scopes, ownership, key-scoping & sanitization helpers
  logger.ts            stderr-only structured logger
  auth/oauth.ts        JWT validation (algorithm-pinned) + cached JWKS
  middleware/          retry.ts, rate-limiter.ts
  clients/             s3/dynamo/slack factories
  tools/               one file per tool + index.ts

Available Tools

6 tools
annotation_statusA

Read or update an annotation task's status, optionally notifying Slack.

ParametersJSON Schema
NameRequiredDescriptionDefault
taskIdYesAnnotation task identifier (Dynamo 'id')
newStatusNoIf provided, update the task status
notifyNoIf true, post the status to Slack

TDQS

A3.5/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must fully disclose behavior. It indicates read or update, but omits details like required permissions, whether reading requires no updates, side effects of Slack notification (e.g., which channel is used), and if the operation is idempotent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Single, clear sentence with no unnecessary words. Front-loads the core purpose (read or update) and optional feature.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description should indicate what the tool returns (e.g., updated status or current status). It fails to do so, leaving the response behavior ambiguous. However, it covers the core functionality adequately.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the description adds no new parameter meaning beyond the structured fields. The mention of 'optionally notifying Slack' aligns with the notify parameter's description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reads or updates an annotation task's status, with optional Slack notification. It distinguishes from sibling tools like dynamo_read/write by focusing on annotation status, and from slack_notify by combining status update with notification.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for annotation status operations but does not explicitly state when to use this tool over siblings (e.g., dynamo_read/write for generic data, slack_notify for standalone notifications). No when-not-to-use guidance provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dynamo_readA

Read a single record from DynamoDB by primary key 'id'.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesPrimary key (partition key 'id') of the record
consistentReadNoUse strongly consistent read

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It discloses the tool is read-only (non-destructive) but does not mention any behavioral traits like rate limits, consistency modes beyond the parameter, or error handling. The description is minimal but not contradictory.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence that is concise and front-loaded with the verb 'Read'. Every word serves a purpose, no fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with two parameters and no output schema, the description covers the essential purpose and key. Could optionally mention the return format, but it's not required for a straightforward read operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% with both parameters documented. The description adds 'by primary key id' which matches the schema, but does not add new meaning beyond what the schema provides. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states 'Read a single record from DynamoDB by primary key id', which specifies the verb (read), resource (DynamoDB record), and the unique key. This distinguishes it from sibling tools like dynamo_write (write) and s3_download (different service).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage when you have a specific 'id' to retrieve a single record. It does not explicitly state when not to use (e.g., for batch reads) but the purpose is clear enough for basic selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

dynamo_writeC

Write a record to DynamoDB keyed by 'id'.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesPrimary key 'id' for the record
attributesYesArbitrary attributes to store with the record
overwriteNoIf false, fail when item already exists

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must disclose behavioral traits. It mentions keying by id but omits critical details such as the overwrite parameter's effect (default true) and failure behavior when overwrite is false. No side effects or consistency guarantees are described.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, short sentence that is to the point. It achieves conciseness but could be improved by including more context without sacrificing brevity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, three parameters (including a nested object), and no annotations, the description is incomplete. It does not explain return values, error behavior, or what happens on duplicate ids.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already adequately describes all three parameters. The description adds 'keyed by id' which is redundant with the schema's primary key description, providing no additional meaning.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Write' and the resource 'a record to DynamoDB keyed by id', distinguishing it from sibling dynamo_read. However, it does not mention the overwrite behavior or additional attributes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives like dynamo_read or s3_upload. There are no explicit when-not or context clues.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

s3_downloadB

Download an object from S3 and return it base64-encoded.

ParametersJSON Schema
NameRequiredDescriptionDefault
keyYesS3 object key/path to fetch
bucketNoOverride default S3 bucket

TDQS

B3.3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It reveals that the output is base64-encoded, which is key behavior, but does not mention limitations (e.g., file size, timeout), permissions required, or whether the tool is safe/read-only. The description adds some value beyond the schema but is insufficient for a no-annotation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that conveys the essential purpose and key detail (base64 encoding). No extraneous information. Efficient and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has 2 parameters, no output schema, and no annotations, the description is minimal but covers the core functionality. However, it lacks details on error handling, default bucket behavior, or size limits. For a simple download tool, it is adequate but not comprehensive.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema provides descriptions for both parameters ('key' and 'bucket') with 100% coverage. The description does not add new meaning beyond the schema; it only restates the purpose. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description explicitly states 'Download an object from S3 and return it base64-encoded', which is a specific verb ('download'), resource ('S3 object'), and adds detail about encoding. This clearly distinguishes it from sibling tools like s3_upload (upload) and other non-S3 tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives. For example, it does not mention that this is for small objects due to base64 encoding, or contrast with other data access tools like dynamo_read. The description lacks context for when this tool is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

s3_uploadB

Upload a base64-encoded object to S3.

ParametersJSON Schema
NameRequiredDescriptionDefault
keyYesS3 object key/path
contentBase64YesFile contents, base64-encoded
contentTypeNoMIME type, e.g. image/pngapplication/octet-stream
bucketNoOverride default S3 bucket

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided. The description implies a write operation but omits important behavior: overwrite semantics, permission requirements, error handling, or side effects like triggering bucket events. The base64 overhead and potential size limits are not mentioned.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no wasted words. It is front-loaded with the action and resource. Slightly more detail could be added without sacrificing conciseness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema and no annotations, the description should provide more context about the return value, default bucket behavior, and MIME type handling. It lacks completeness for a tool with four parameters.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds no additional meaning beyond the parameter names and types already in the schema. It does not clarify the relationship between 'key' and 'bucket' or the necessity of valid base64 in 'contentBase64'.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Upload'), the resource ('object to S3'), and the encoding requirement ('base64-encoded'). It distinguishes from the sibling tool s3_download, which downloads objects.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives, prerequisites (e.g., AWS credentials), or constraints (e.g., object size limits). The description does not contrast with s3_download or other siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

slack_notifyA

Post a message to a Slack channel.

ParametersJSON Schema
NameRequiredDescriptionDefault
messageYesText to post to Slack
channelNoChannel ID or name; defaults to SLACK_DEFAULT_CHANNEL
threadTsNoOptional parent message ts to reply in a thread

TDQS

A3.5/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must fully convey behavioral traits. It only states the basic action without mentioning authentication requirements, rate limits, whether messages are formatted, or error behavior. This is insufficient for an agent to understand side effects or constraints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence that front-loads the action and resource. Every word is necessary and there is no wasted text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of an output schema and annotations, the description is minimal. It covers the core purpose but lacks details on limitations (e.g., message length), return values, or confirmation of success. It is adequate for a simple tool but not comprehensive.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%; the schema already documents each parameter (message, channel, threadTs) with descriptions. The tool description adds no additional meaning beyond what is in the schema, so a baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Post a message') and the resource ('to a Slack channel'). It is a specific verb+resource pair that distinguishes this tool from siblings like dynamo_read or s3_upload, which operate on different services.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description does not provide explicit guidance on when to use this tool versus alternatives. While the siblings are dissimilar, there is no mention of prerequisites or context (e.g., 'use this to send notifications, not for file uploads'). Usage is implied but not clarified.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 6 tool updatesv1.0.0
    • First observedannotation_status
    • First observeddynamo_read
    • First observeddynamo_write
    • First observeds3_download
    • First observeds3_upload
    • First observedslack_notify

TDQS

A3.5/5.0

Scored across 6 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: annotation_status for task status, dynamo_read/write for database, s3_download/upload for storage, slack_notify for messaging. No overlap.

Naming Consistency4/5

Most tools follow verb_noun pattern (dynamo_read, dynamo_write, s3_download, s3_upload, slack_notify), but 'annotation_status' is an outlier as a noun phrase.

Tool Count5/5

6 tools is well-scoped for a server covering annotation, database, storage, and Slack integration. It feels neither too few nor too many.

Completeness3/5

Each service has basic operations but lacks deletions, updates, and lists. For annotation only status is provided. This leaves notable gaps for typical workflows, but the server may be intentionally scoped.

Maintenance

ActivityInactive
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    A production-grade MCP server that enables AI assistants like Claude to read Etsy shop data—listings, orders, inventory, and stats—through eight read-only tools, using OAuth and automatic pagination.
    8
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A production-ready MCP server for the Slack API that enables searching, listing channels, reading history, inspecting users, fetching threads, and sending messages through controlled Slack tools.
    21,724 npm
    MIT
  • F
    license
    B
    quality
    B
    maintenance
    Unified MCP server exposing 12 DevOps tools across GitHub, PostgreSQL, Slack, and Google Calendar for AI agents, with rate limiting, input validation, and per-service scoped tokens.
    30
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    A remote, OAuth2-authenticated MCP server that provides read-only S3 tools (list buckets, list objects, check public access, get bucket size) designed to run on ECS Fargate with Auth0 as the identity provider.
    -