custom-mcp-server
Allows sending notifications and updating task status with Slack messages, including posting to channels and threads.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@custom-mcp-serverUpdate annotation task task-123 to completed and notify Slack."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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: production-grade-mcp-agentic-system
Install
npm installEnvironment setup
Copy .env.example to .env and fill in the values. Keys:
Key | Required by | Notes |
| all AWS tools | e.g. |
| all AWS tools | secret — keep out of source control |
| all AWS tools | secret |
|
| default bucket |
|
| table with partition key |
|
| secret, |
|
| e.g. |
| auth (every call) | expected |
| auth (every call) | expected |
| auth (RS256) | JWKS endpoint for signature verification |
| auth (HS256, dev only) | optional; ≥ 32 chars; refused when |
| auth | set to |
| rate limiter | default |
| retry | default |
| retry | default |
| logger |
|
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 |
|
|
| Upload base64 content to S3 under the caller's prefix; returns |
|
|
| Download object from the caller's prefix; returns |
|
|
| Read a record the caller owns; returns the item (without |
|
|
| Put a record stamped with the caller as |
|
|
| Post to Slack (text sanitized); returns |
|
|
| 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(optionallyBearer-prefixed). The expected algorithm is pinned from server configuration — not the token header — to block algorithm-confusion attacks: RS256 (verified againstJWKS_URI) by default, or HS256 only when aJWT_SECRET(≥ 32 chars) is set andNODE_ENVis notproduction. The server checksiss/aud/expiry (with a small clock skew) and derives anAuthContext(subject,scopes). Invalid tokens →AUTH_INVALID.Scope authorization. Each tool declares
requiredScopes. A token missing a required scope is rejected withFORBIDDENbefore the handler runs.Object-level authorization (ownership). DynamoDB records carry an
ownerattribute 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
codeplus arequestId; 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:toolso one caller cannot starve others. Unknown tool names are rejected before consuming limiter budget. Exceeding it yieldsRATE_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.tsMaintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/satyamsh04/custom-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server