Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const TIP_MESSAGE_LIMIT = 300;
/** When `false`, `resolveServerMode('auto', ...)` forces {@link SERVER_MODE.DEFAULT} regardless of client capabilities. */
export const SERVER_MODE_AUTO_DETECTION_ENABLED = true;

/** Matches the Apify API's payload limit (`MAX_PAYLOAD_SIZE_BYTES` in `@apify/consts`): no larger body can reach it. */
export const MAX_REQUEST_BODY_BYTES = 9 * 1024 * 1024;

export const SERVER_NAME = 'apify-mcp-server';
export const SERVER_TITLE = 'Apify MCP Server';
export const HELPER_TOOLS = {
Expand Down
4 changes: 3 additions & 1 deletion src/dev_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import log from '@apify/log';
import { parseBooleanOrNull } from '@apify/utilities';

import { ApifyClient } from './apify_client.js';
import { MAX_REQUEST_BODY_BYTES } from './const.js';
import { buildMcpClientContext } from './mcp/client_context.js';
import { ActorsMcpServer } from './mcp/server.js';
import { createStatelessServer } from './mcp/stateless_server.js';
Expand Down Expand Up @@ -249,7 +250,8 @@ export function createExpressApp(): express.Express {
if (validateHost(req, res) && validateOrigin(req, res)) next();
});

app.use(express.json());
// Express's 100 KB default is too small for tool calls that carry files. See apify/apify-mcp-server#1610.
app.use(express.json({ limit: MAX_REQUEST_BODY_BYTES }));
app.post('/', async (req: Request, res: Response) => {
log.info('Received MCP request:', req.body);
try {
Expand Down
43 changes: 41 additions & 2 deletions tests/unit/dev_server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi }

import log from '@apify/log';

import { HELPER_TOOLS } from '../../src/const.js';
import { HELPER_TOOLS, MAX_REQUEST_BODY_BYTES } from '../../src/const.js';
import {
createExpressApp,
extractInitializeMessage,
Expand All @@ -20,7 +20,12 @@ import {
} from '../../src/dev_server.js';
import type * as ToolsLoaderModule from '../../src/utils/tools_loader.js';
import { getActors } from '../../src/utils/tools_loader.js';
import { makeArgsRecorderTool, readJsonRpcPayload, STATELESS_PROTOCOL_VERSION } from './helpers/mcp_server.js';
import {
makeArgsRecorderTool,
makeRecorderTool,
readJsonRpcPayload,
STATELESS_PROTOCOL_VERSION,
} from './helpers/mcp_server.js';

vi.mock('../../src/utils/tools_loader.js', async (importOriginal) => {
const actual = await importOriginal<typeof ToolsLoaderModule>();
Expand Down Expand Up @@ -435,4 +440,38 @@ describe('createExpressApp() era routing', () => {
expect(getActorsMock).not.toHaveBeenCalled();
});
});

it('accepts a 4 MiB tool call body, the size of a 3 MiB version sent as base64', async () => {
const { tool, received } = makeRecorderTool('source-tool');
getActorsMock.mockResolvedValue([tool]);

await withDevServer(async (post) => {
const { body, headers } = statelessRequest('tools/call', {
name: tool.name,
arguments: { content: 'x'.repeat(4 * 1024 * 1024) },
});

const response = await post(DEV_URL, body, { ...headers, authorization: 'Bearer dev-token' });

expect(response.status).toBe(200);
expect(received.called).toBe(true);
});
});

it('rejects a body over MAX_REQUEST_BODY_BYTES with 413', async () => {
const { tool, received } = makeRecorderTool('source-tool');
getActorsMock.mockResolvedValue([tool]);

await withDevServer(async (post) => {
const { body, headers } = statelessRequest('tools/call', {
name: tool.name,
arguments: { content: 'x'.repeat(MAX_REQUEST_BODY_BYTES) },
});

const response = await post(DEV_URL, body, { ...headers, authorization: 'Bearer dev-token' });

expect(response.status).toBe(413);
expect(received.called).toBe(false);
});
});
});
Loading