Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Dependencies
node_modules/
**/node_modules/

# Build outputs
dist/
**/dist/

# Git
.git/
.gitignore

# CI/CD
.github/
.gitlab-ci.yml
.travis.yml

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Testing
test/
tests/
__tests__/
*.test.js
*.spec.js
coverage/
.nyc_output/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment
.env
.env.*

# Temporary files
*.tmp
*.temp
.cache/

# Examples and scripts
examples/
bin/

# Other packages (we only need mcp-server)
packages/*/
!packages/mcp-server/
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "7.3.0"
".": "7.4.0"
}
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Changelog

## 7.4.0 (2026-02-11)

Full Changelog: [v7.3.0...v7.4.0](https://github.com/imagekit-developer/imagekit-nodejs/compare/v7.3.0...v7.4.0)

### Features

* **mcp:** add initial server instructions ([cdce131](https://github.com/imagekit-developer/imagekit-nodejs/commit/cdce131dc17fba5469393a285ac536acd74742b2))


### Bug Fixes

* **client:** avoid memory leak with abort signals ([c08f7c0](https://github.com/imagekit-developer/imagekit-nodejs/commit/c08f7c04267e000d51cfad22ec8337e456d20171))
* **client:** avoid removing abort listener too early ([0738e88](https://github.com/imagekit-developer/imagekit-nodejs/commit/0738e8884a59ddac579fab6a65e0221fdff4247c))


### Chores

* **client:** do not parse responses with empty content-length ([4b5fcbf](https://github.com/imagekit-developer/imagekit-nodejs/commit/4b5fcbfd1188573ccd1cea40b8e4924a5e2051dc))
* **client:** restructure abort controller binding ([46c04e1](https://github.com/imagekit-developer/imagekit-nodejs/commit/46c04e16c46bca7bc1b0383d151f027d7d918611))
* **internal:** add health check to MCP server when running in HTTP mode ([83d1174](https://github.com/imagekit-developer/imagekit-nodejs/commit/83d1174751241a66748b9d0f4b2b92f37715d4ad))
* **internal:** allow basic filtering of methods allowed for MCP code mode ([4a86182](https://github.com/imagekit-developer/imagekit-nodejs/commit/4a861827d463d2b6e9812a4aa58d2df14cb356bf))
* **internal:** always generate MCP server dockerfiles and upgrade associated dependencies ([90eae18](https://github.com/imagekit-developer/imagekit-nodejs/commit/90eae18e29708d7596a6e783cad196c9a4f75f39))
* **internal:** avoid type checking errors with ts-reset ([7cd3980](https://github.com/imagekit-developer/imagekit-nodejs/commit/7cd398067ad0736b67bfb3d8ace58d15a94c1fd2))
* **internal:** refactor flag parsing for MCP servers and add debug flag ([ff4b97e](https://github.com/imagekit-developer/imagekit-nodejs/commit/ff4b97e40fb46ca0b4f3229074c3f614b045641c))
* **internal:** support oauth authorization code flow for MCP servers ([5f6c688](https://github.com/imagekit-developer/imagekit-nodejs/commit/5f6c688f4f41df60d88fce94bc10cfdce4e29d78))
* **internal:** upgrade hono ([61a5d88](https://github.com/imagekit-developer/imagekit-nodejs/commit/61a5d8863e4fcb692d187bb0a7b44e1788faf8ee))

## 7.3.0 (2026-02-02)

Full Changelog: [v7.2.2...v7.3.0](https://github.com/imagekit-developer/imagekit-nodejs/compare/v7.2.2...v7.3.0)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@imagekit/nodejs",
"version": "7.3.0",
"version": "7.4.0",
"description": "Offical NodeJS SDK for ImageKit.io integration",
"author": "Image Kit <developer@imagekit.io>",
"types": "dist/index.d.ts",
Expand Down
71 changes: 71 additions & 0 deletions packages/mcp-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Dockerfile for Image Kit MCP Server
#
# This Dockerfile builds a Docker image for the MCP Server.
#
# To build the image locally:
# docker build -f packages/mcp-server/Dockerfile -t @imagekit/api-mcp:local .
#
# To run the image:
# docker run -i @imagekit/api-mcp:local [OPTIONS]
#
# Common options:
# --tool=<name> Include specific tools
# --resource=<name> Include tools for specific resources
# --operation=read|write Filter by operation type
# --client=<type> Set client compatibility (e.g., claude, cursor)
# --transport=<type> Set transport type (stdio or http)
#
# For a full list of options:
# docker run -i @imagekit/api-mcp:local --help
#
# Note: The MCP server uses stdio transport by default. Docker's -i flag
# enables interactive mode, allowing the container to communicate over stdin/stdout.

# Build stage
FROM node:24-alpine AS builder

# Install bash for build script
RUN apk add --no-cache bash openssl

# Set working directory
WORKDIR /build

# Copy entire repository
COPY . .

# Install all dependencies and build everything
RUN yarn install --frozen-lockfile && \
yarn build

# Production stage
FROM node:24-alpine

# Add non-root user
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001

# Set working directory
WORKDIR /app

# Copy the built mcp-server dist directory
COPY --from=builder /build/packages/mcp-server/dist ./

# Copy node_modules from mcp-server (includes all production deps)
COPY --from=builder /build/packages/mcp-server/node_modules ./node_modules

# Copy the built @imagekit/nodejs into node_modules
COPY --from=builder /build/dist ./node_modules/@imagekit/nodejs

# Change ownership to nodejs user
RUN chown -R nodejs:nodejs /app

# Switch to non-root user
USER nodejs

# The MCP server uses stdio transport by default
# No exposed ports needed for stdio communication

# Set the entrypoint to the MCP server
ENTRYPOINT ["node", "index.js"]

# Allow passing arguments to the MCP server
CMD []
2 changes: 1 addition & 1 deletion packages/mcp-server/cloudflare-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@cloudflare/workers-oauth-provider": "^0.0.5",
"@modelcontextprotocol/sdk": "^1.25.2",
"agents": "^0.0.88",
"hono": "^4.11.4",
"hono": "^4.11.7",
"@imagekit/api-mcp": "latest",
"zod": "^3.24.4"
}
Expand Down
7 changes: 6 additions & 1 deletion packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@imagekit/api-mcp",
"version": "7.3.0",
"version": "7.4.0",
"description": "The official MCP Server for the Image Kit API",
"author": "Image Kit <developer@imagekit.io>",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -34,10 +34,13 @@
"@cloudflare/cabidela": "^0.2.4",
"@modelcontextprotocol/sdk": "^1.25.2",
"@valtown/deno-http-worker": "^0.0.21",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"express": "^5.1.0",
"fuse.js": "^7.1.0",
"jq-web": "https://github.com/stainless-api/jq-web/releases/download/v0.8.8/jq-web.tar.gz",
"morgan": "^1.10.0",
"morgan-body": "^2.6.9",
"qs": "^6.14.1",
"typescript": "5.8.3",
"yargs": "^17.7.2",
Expand All @@ -50,9 +53,11 @@
},
"devDependencies": {
"@anthropic-ai/mcpb": "^2.1.2",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.3",
"@types/jest": "^29.4.0",
"@types/morgan": "^1.9.10",
"@types/qs": "^6.14.0",
"@types/yargs": "^17.0.8",
"@typescript-eslint/eslint-plugin": "8.31.1",
Expand Down
21 changes: 20 additions & 1 deletion packages/mcp-server/src/code-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { McpTool, Metadata, ToolCallResult, asErrorResult, asTextContentResult }
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { readEnv, requireValue } from './server';
import { WorkerInput, WorkerOutput } from './code-tool-types';
import { SdkMethod } from './methods';
import { ImageKit } from '@imagekit/nodejs';

const prompt = `Runs JavaScript code to interact with the Image Kit API.
Expand Down Expand Up @@ -35,7 +36,7 @@ Variables will not persist between calls, so make sure to return or log any data
*
* @param endpoints - The endpoints to include in the list.
*/
export function codeTool(): McpTool {
export function codeTool(params: { blockedMethods: SdkMethod[] | undefined }): McpTool {
const metadata: Metadata = { resource: 'all', operation: 'write', tags: [] };
const tool: Tool = {
name: 'execute',
Expand All @@ -59,6 +60,24 @@ export function codeTool(): McpTool {
const code = args.code as string;
const intent = args.intent as string | undefined;

// Do very basic blocking of code that includes forbidden method names.
//
// WARNING: This is not secure against obfuscation and other evasion methods. If
// stronger security blocks are required, then these should be enforced in the downstream
// API (e.g., by having users call the MCP server with API keys with limited permissions).
if (params.blockedMethods) {
const blockedMatches = params.blockedMethods.filter((method) =>
code.includes(method.fullyQualifiedName),
);
if (blockedMatches.length > 0) {
return asErrorResult(
`The following methods have been blocked by the MCP server and cannot be used in code execution: ${blockedMatches
.map((m) => m.fullyQualifiedName)
.join(', ')}`,
);
}
}

// this is not required, but passing a Stainless API key for the matching project_name
// will allow you to run code-mode queries against non-published versions of your SDK.
const stainlessAPIKey = readEnv('STAINLESS_API_KEY');
Expand Down
4 changes: 3 additions & 1 deletion packages/mcp-server/src/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { IncomingMessage } from 'node:http';
import { ClientOptions } from '@imagekit/nodejs';

export const parseAuthHeaders = (req: IncomingMessage): Partial<ClientOptions> => {
export const parseAuthHeaders = (req: IncomingMessage, required?: boolean): Partial<ClientOptions> => {
if (req.headers.authorization) {
const scheme = req.headers.authorization.split(' ')[0]!;
const value = req.headers.authorization.slice(scheme.length + 1);
Expand All @@ -19,6 +19,8 @@ export const parseAuthHeaders = (req: IncomingMessage): Partial<ClientOptions> =
'Unsupported authorization scheme. Expected the "Authorization" header to be a supported scheme (Basic).',
);
}
} else if (required) {
throw new Error('Missing required Authorization header; see WWW-Authenticate header for details.');
}

const privateKey =
Expand Down
50 changes: 37 additions & 13 deletions packages/mcp-server/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,31 @@

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';

import express from 'express';
import morgan from 'morgan';
import morganBody from 'morgan-body';
import { McpOptions } from './options';
import { ClientOptions, initMcpServer, newMcpServer } from './server';
import { parseAuthHeaders } from './headers';

const newServer = ({
const newServer = async ({
clientOptions,
mcpOptions,
req,
res,
}: {
clientOptions: ClientOptions;
mcpOptions: McpOptions;
req: express.Request;
res: express.Response;
}): McpServer | null => {
const server = newMcpServer();
}): Promise<McpServer | null> => {
const server = await newMcpServer();

try {
const authOptions = parseAuthHeaders(req);
initMcpServer({
const authOptions = parseAuthHeaders(req, false);
await initMcpServer({
server: server,
mcpOptions: mcpOptions,
clientOptions: {
...clientOptions,
...authOptions,
Expand All @@ -45,7 +49,7 @@ const newServer = ({
const post =
(options: { clientOptions: ClientOptions; mcpOptions: McpOptions }) =>
async (req: express.Request, res: express.Response) => {
const server = newServer({ ...options, req, res });
const server = await newServer({ ...options, req, res });
// If we return null, we already set the authorization error.
if (server === null) return;
const transport = new StreamableHTTPServerTransport();
Expand Down Expand Up @@ -75,32 +79,52 @@ const del = async (req: express.Request, res: express.Response) => {

export const streamableHTTPApp = ({
clientOptions = {},
mcpOptions = {},
mcpOptions,
debug,
}: {
clientOptions?: ClientOptions;
mcpOptions?: McpOptions;
mcpOptions: McpOptions;
debug: boolean;
}): express.Express => {
const app = express();
app.set('query parser', 'extended');
app.use(express.json());

if (debug) {
morganBody(app, {
logAllReqHeader: true,
logAllResHeader: true,
logRequestBody: true,
logResponseBody: true,
});
} else {
app.use(morgan('combined'));
}

app.get('/health', async (req: express.Request, res: express.Response) => {
res.status(200).send('OK');
});
app.get('/', get);
app.post('/', post({ clientOptions, mcpOptions }));
app.delete('/', del);

return app;
};

export const launchStreamableHTTPServer = async (options: McpOptions, port: number | string | undefined) => {
const app = streamableHTTPApp({ mcpOptions: options });
const server = app.listen(port);
export const launchStreamableHTTPServer = async (params: {
mcpOptions: McpOptions;
debug: boolean;
port: number | string | undefined;
}) => {
const app = streamableHTTPApp({ mcpOptions: params.mcpOptions, debug: params.debug });
const server = app.listen(params.port);
const address = server.address();

if (typeof address === 'string') {
console.error(`MCP Server running on streamable HTTP at ${address}`);
} else if (address !== null) {
console.error(`MCP Server running on streamable HTTP on port ${address.port}`);
} else {
console.error(`MCP Server running on streamable HTTP on port ${port}`);
console.error(`MCP Server running on streamable HTTP on port ${params.port}`);
}
};
Loading