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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
},
"dependencies": {
"@alcyone-labs/zod-to-json-schema": "4.0.10",
"@iterable/api": "0.3.0",
"@iterable/api": "0.4.0",
"@modelcontextprotocol/sdk": "1.18.1",
"@primno/dpapi": "^2.0.1",
"@types/json-schema": "7.0.15",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export interface McpServerConfig {
readonly allowWrites: boolean;
/** If false, exclude tools that can send messages (campaign/journey/event-triggered sends) */
readonly allowSends: boolean;
/** The active Iterable API key (optional for tests) */
readonly apiKey?: string;
/** The Iterable API base URL (defaults to https://api.iterable.com) */
readonly baseUrl?: string;
/** The active Iterable API key */
readonly apiKey: string;
/** The Iterable API base URL */
readonly baseUrl: string;
}

export function resolveAllowFlags(
Expand Down
2 changes: 1 addition & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ export class IterableMcpServer {
// Initialize Iterable client with API key and base URL from config
this.iterableClient = new IterableClient({
apiKey: mcpConfig.apiKey,
baseURL: mcpConfig.baseUrl || "https://api.iterable.com",
baseUrl: mcpConfig.baseUrl,
});
this.setupHttpInterceptors();

Expand Down
5 changes: 3 additions & 2 deletions tests/unit/prompts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { beforeEach, describe, expect, it } from "@jest/globals";
import { generatePromptMessage, generatePrompts } from "../../src/prompts.js";
import { filterTools } from "../../src/tool-filter.js";
import { createAllTools } from "../../src/tools/index.js";
import { createTestConfig } from "../utils/test-config.js";

describe("MCP Prompts", () => {
let client: IterableClient;
Expand Down Expand Up @@ -115,11 +116,11 @@ describe("MCP Prompts", () => {
it("should work with filtered read-only tools", () => {
// Test the pattern used in the server: filter to read-only tools first
const allTools = createAllTools(client);
const readOnlyConfig = {
const readOnlyConfig = createTestConfig({
allowUserPii: true,
allowWrites: false,
allowSends: false,
};
});
const readOnlyTools = filterTools(allTools, readOnlyConfig);
const prompts = generatePrompts(readOnlyTools);

Expand Down
29 changes: 18 additions & 11 deletions tests/unit/tool-filter-defaults.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { filterTools } from "../../src/tool-filter";
import { filterTools } from "../../src/tool-filter.js";
import { createTestConfig } from "../utils/test-config.js";

describe("filterTools defaults", () => {
const readOnlyTool: any = {
Expand All @@ -16,21 +17,27 @@ describe("filterTools defaults", () => {
} as any;

it("excludes write tools when allowWrites=false", () => {
const out = filterTools([readOnlyTool, writeTool], {
allowUserPii: false,
allowWrites: false,
allowSends: true,
});
const out = filterTools(
[readOnlyTool, writeTool],
createTestConfig({
allowUserPii: false,
allowWrites: false,
allowSends: true,
})
);
expect(out.map((t) => t.name)).toContain("get_campaigns");
expect(out.map((t) => t.name)).not.toContain("create_campaign");
});

it("includes write tools when allowWrites=true", () => {
const out = filterTools([readOnlyTool, writeTool], {
allowUserPii: false,
allowWrites: true,
allowSends: true,
});
const out = filterTools(
[readOnlyTool, writeTool],
createTestConfig({
allowUserPii: false,
allowWrites: true,
allowSends: true,
})
);
expect(out.map((t) => t.name)).toContain("create_campaign");
});
});
37 changes: 19 additions & 18 deletions tests/unit/tool-filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
READ_ONLY_TOOLS,
} from "../../src/tool-filter.js";
import { createAllTools } from "../../src/tools/index.js";
import { createTestConfig } from "../utils/test-config.js";

// Mock Iterable client for testing
const mockClient = {} as any;
Expand All @@ -23,11 +24,11 @@ describe("Tool Filter", () => {
describe("Safe tool lists validation", () => {
it("should have all NON_PII_TOOLS in actual tool names", () => {
// Get the safe non-PII tools by testing with restrictive config
const restrictiveConfig: McpServerConfig = {
const restrictiveConfig: McpServerConfig = createTestConfig({
allowUserPii: false,
allowWrites: true, // Allow writes to isolate PII filtering
allowSends: true,
};
});

const nonPiiTools = filterTools(allTools, restrictiveConfig);
const nonPiiToolNames = nonPiiTools.map((tool) => tool.name);
Expand All @@ -40,11 +41,11 @@ describe("Tool Filter", () => {

it("should have all READ_ONLY_TOOLS in actual tool names", () => {
// Get the read-only tools by testing with restrictive config
const restrictiveConfig: McpServerConfig = {
const restrictiveConfig: McpServerConfig = createTestConfig({
allowUserPii: true, // Allow PII to isolate write filtering
allowWrites: false,
allowSends: true,
};
});

const readOnlyTools = filterTools(allTools, restrictiveConfig);
const readOnlyToolNames = readOnlyTools.map((tool) => tool.name);
Expand All @@ -70,23 +71,23 @@ describe("Tool Filter", () => {
});

it("should filter tools when restrictions are applied", () => {
const permissiveConfig: McpServerConfig = {
const permissiveConfig: McpServerConfig = createTestConfig({
allowUserPii: true,
allowWrites: true,
allowSends: true,
};
});

const restrictivePiiConfig: McpServerConfig = {
const restrictivePiiConfig: McpServerConfig = createTestConfig({
allowUserPii: false,
allowWrites: true,
allowSends: true,
};
});

const restrictiveWriteConfig: McpServerConfig = {
const restrictiveWriteConfig: McpServerConfig = createTestConfig({
allowUserPii: true,
allowWrites: false,
allowSends: true,
};
});

const allToolsCount = filterTools(allTools, permissiveConfig).length;
const nonPiiToolsCount = filterTools(
Expand All @@ -107,11 +108,11 @@ describe("Tool Filter", () => {

describe("Configuration filtering", () => {
it("should filter PII tools when allowUserPii is false", () => {
const config: McpServerConfig = {
const config: McpServerConfig = createTestConfig({
allowUserPii: false,
allowWrites: true,
allowSends: true,
};
});

const filteredTools = filterTools(allTools, config);
const filteredNames = filteredTools.map((tool) => tool.name);
Expand All @@ -134,11 +135,11 @@ describe("Tool Filter", () => {
});

it("should filter write tools when allowWrites is false", () => {
const config: McpServerConfig = {
const config: McpServerConfig = createTestConfig({
allowUserPii: true,
allowWrites: false,
allowSends: true,
};
});

const filteredTools = filterTools(allTools, config);
const filteredNames = filteredTools.map((tool) => tool.name);
Expand All @@ -163,22 +164,22 @@ describe("Tool Filter", () => {
});

it("should allow all tools when both restrictions are disabled", () => {
const config: McpServerConfig = {
const config: McpServerConfig = createTestConfig({
allowUserPii: true,
allowWrites: true,
allowSends: true,
};
});

const filteredTools = filterTools(allTools, config);
expect(filteredTools).toHaveLength(allTools.length);
});

it("should apply both restrictions when both are enabled", () => {
const config: McpServerConfig = {
const config: McpServerConfig = createTestConfig({
allowUserPii: false,
allowWrites: false,
allowSends: false,
};
});

const filteredTools = filterTools(allTools, config);

Expand Down
17 changes: 17 additions & 0 deletions tests/utils/test-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { McpServerConfig } from "../../src/config.js";

/**
* Helper to create test config with required fields
*/
export function createTestConfig(
overrides: Partial<McpServerConfig> = {}
): McpServerConfig {
return {
apiKey: "test-api-key",
baseUrl: "https://api.iterable.com",
allowUserPii: false,
allowWrites: false,
allowSends: false,
...overrides,
};
}