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
7 changes: 0 additions & 7 deletions packages/mcp-server/.dockerignore → .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ node_modules/
# Build outputs
dist/
**/dist/
build/
**/build/

# Git
.git/
Expand All @@ -28,11 +26,6 @@ build/
.DS_Store
Thumbs.db

# Documentation
*.md
docs/
LICENSE

# Testing
test/
tests/
Expand Down
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.19.0"
".": "0.19.1"
}
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 0.19.1 (2026-02-14)

Full Changelog: [v0.19.0...v0.19.1](https://github.com/ArkHQ-io/ark-nodejs/compare/v0.19.0...v0.19.1)

### Chores

* **internal:** allow basic filtering of methods allowed for MCP code mode ([2a08f18](https://github.com/ArkHQ-io/ark-nodejs/commit/2a08f182832a256142ddf2d829710a6f66149d47))
* **internal:** always generate MCP server dockerfiles and upgrade associated dependencies ([037403e](https://github.com/ArkHQ-io/ark-nodejs/commit/037403eac179b8c1aaa756746276de7c4de38f04))
* **internal:** avoid type checking errors with ts-reset ([9496839](https://github.com/ArkHQ-io/ark-nodejs/commit/9496839d6222a21c3c9aa676282cfa8e04d6edaf))
* **internal:** improve layout of generated MCP server files ([20ba475](https://github.com/ArkHQ-io/ark-nodejs/commit/20ba4751619d64194cafc11340b29e0eb0cf830e))
* **internal:** improve reliability of MCP servers when using local code mode execution ([00be033](https://github.com/ArkHQ-io/ark-nodejs/commit/00be033fe6e3341ef30fa2aebff9f343410ec1ee))
* **mcp:** forward STAINLESS_API_KEY to docs search endpoint ([6315d60](https://github.com/ArkHQ-io/ark-nodejs/commit/6315d6035354116ec682d5436b31c196070c4574))

## 0.19.0 (2026-02-07)

Full Changelog: [v0.18.0...v0.19.0](https://github.com/ArkHQ-io/ark-nodejs/compare/v0.18.0...v0.19.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": "ark-email",
"version": "0.19.0",
"version": "0.19.1",
"description": "The official TypeScript library for the Ark API",
"author": "Ark <hi@arkhq.io>",
"types": "dist/index.d.ts",
Expand Down
16 changes: 8 additions & 8 deletions packages/mcp-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
# enables interactive mode, allowing the container to communicate over stdin/stdout.

# Build stage
FROM node:20-alpine AS builder
FROM node:24-alpine AS builder

# Enable corepack to use pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate

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

Expand All @@ -35,25 +36,24 @@ WORKDIR /build
# Copy entire repository
COPY . .

# Set CI environment variable so pnpm install runs without prompts
ENV CI=true

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

# Production stage
FROM node:20-alpine
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 preserving directory structure
COPY --from=builder /build/packages/mcp-server/dist ./packages/mcp-server/dist
COPY --from=builder /build/packages/mcp-server/node_modules ./packages/mcp-server/node_modules

# Copy node_modules from root (pnpm hoists dependencies here)
COPY --from=builder /build/node_modules ./node_modules
# Copy the build results, preserving directory structure
COPY --from=builder /build .

# Copy the built ark-email into node_modules
COPY --from=builder /build/dist ./node_modules/ark-email
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ark-email-mcp",
"version": "0.19.0",
"version": "0.19.1",
"description": "The official MCP Server for the Ark API",
"author": "Ark <hi@arkhq.io>",
"types": "dist/index.d.ts",
Expand Down
File renamed without changes.
23 changes: 21 additions & 2 deletions packages/mcp-server/src/code-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import { McpTool, Metadata, ToolCallResult, asErrorResult, asTextContentResult } from './types';
import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { readEnv, requireValue } from './server';
import { readEnv, requireValue } from './util';
import { WorkerInput, WorkerOutput } from './code-tool-types';
import { SdkMethod } from './methods';
import { Ark } from 'ark-email';

const prompt = `Runs JavaScript code to interact with the Ark API.
Expand Down Expand Up @@ -42,7 +43,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 @@ -66,6 +67,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
8 changes: 7 additions & 1 deletion packages/mcp-server/src/docs-search-tool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { Metadata, asTextContentResult } from './types';
import { readEnv } from './util';

import { Tool } from '@modelcontextprotocol/sdk/types.js';

Expand Down Expand Up @@ -45,7 +46,12 @@ const docsSearchURL =
export const handler = async (_: unknown, args: Record<string, unknown> | undefined) => {
const body = args as any;
const query = new URLSearchParams(body).toString();
const result = await fetch(`${docsSearchURL}?${query}`);
const stainlessAPIKey = readEnv('STAINLESS_API_KEY');
const result = await fetch(`${docsSearchURL}?${query}`, {
headers: {
...(stainlessAPIKey && { Authorization: stainlessAPIKey }),
},
});

if (!result.ok) {
throw new Error(
Expand Down
8 changes: 6 additions & 2 deletions packages/mcp-server/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { ClientOptions } from 'ark-email';
import express from 'express';
import morgan from 'morgan';
import morganBody from 'morgan-body';
import { parseAuthHeaders } from './auth';
import { McpOptions } from './options';
import { ClientOptions, initMcpServer, newMcpServer } from './server';
import { parseAuthHeaders } from './headers';
import { initMcpServer, newMcpServer } from './server';

const newServer = async ({
clientOptions,
mcpOptions,
req,
res,
}: {
clientOptions: ClientOptions;
mcpOptions: McpOptions;
req: express.Request;
res: express.Response;
}): Promise<McpServer | null> => {
Expand All @@ -24,6 +27,7 @@ const newServer = async ({
const authOptions = parseAuthHeaders(req, false);
await initMcpServer({
server: server,
mcpOptions: mcpOptions,
clientOptions: {
...clientOptions,
...authOptions,
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async function main() {

switch (options.transport) {
case 'stdio':
await launchStdioServer();
await launchStdioServer(options);
break;
case 'http':
await launchStreamableHTTPServer({
Expand Down
Loading
Loading