From d5f518bcde7af5fc75c7c4ee4ae120c3d6438c6a Mon Sep 17 00:00:00 2001 From: Ness Li Date: Thu, 4 Jun 2026 20:41:28 +0100 Subject: [PATCH 1/6] feat: add on-prem agents and connected systems API + MCP tools Add Agents and ConnectedSystems endpoints aligned with imt-web-api (POST /agent/register for create). Expose on-prem-agent_* and connected-system_* MCP tools plus connected-system_list-apps enum. Bump version to 1.5.0. Co-authored-by: Cursor --- .gitignore | 3 +- package-lock.json | 4 +- package.json | 2 +- src/endpoints/agents.ts | 206 ++++++++++++++++++++ src/endpoints/connected-systems.tools.ts | 205 +++++++++++++++++++ src/endpoints/connected-systems.ts | 165 ++++++++++++++++ src/endpoints/enums.tools.ts | 21 ++ src/endpoints/enums.ts | 27 +++ src/endpoints/on-prem-agents.tools.ts | 172 ++++++++++++++++ src/index.ts | 18 +- src/make.ts | 16 ++ src/tools.ts | 4 + test/agents.spec.ts | 87 +++++++++ test/connected-systems.spec.ts | 84 ++++++++ test/enums.spec.ts | 8 + test/mcp.spec.ts | 24 +++ test/mocks/agents/app-config.json | 22 +++ test/mocks/agents/create.json | 11 ++ test/mocks/agents/delete.json | 3 + test/mocks/agents/get.json | 12 ++ test/mocks/agents/list.json | 14 ++ test/mocks/agents/update.json | 10 + test/mocks/connected-systems/create.json | 12 ++ test/mocks/connected-systems/delete.json | 3 + test/mocks/connected-systems/get.json | 12 ++ test/mocks/connected-systems/list.json | 14 ++ test/mocks/connected-systems/update.json | 8 + test/mocks/enums/connected-system-apps.json | 14 ++ 28 files changed, 1176 insertions(+), 5 deletions(-) create mode 100644 src/endpoints/agents.ts create mode 100644 src/endpoints/connected-systems.tools.ts create mode 100644 src/endpoints/connected-systems.ts create mode 100644 src/endpoints/on-prem-agents.tools.ts create mode 100644 test/agents.spec.ts create mode 100644 test/connected-systems.spec.ts create mode 100644 test/mocks/agents/app-config.json create mode 100644 test/mocks/agents/create.json create mode 100644 test/mocks/agents/delete.json create mode 100644 test/mocks/agents/get.json create mode 100644 test/mocks/agents/list.json create mode 100644 test/mocks/agents/update.json create mode 100644 test/mocks/connected-systems/create.json create mode 100644 test/mocks/connected-systems/delete.json create mode 100644 test/mocks/connected-systems/get.json create mode 100644 test/mocks/connected-systems/list.json create mode 100644 test/mocks/connected-systems/update.json create mode 100644 test/mocks/enums/connected-system-apps.json diff --git a/.gitignore b/.gitignore index 77652cd..e082f38 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ coverage .env node_modules/* dist -docs \ No newline at end of file +docs +makehq-sdk-*.tgz \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 567cfcd..9114e4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@makehq/sdk", - "version": "1.4.0", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@makehq/sdk", - "version": "1.4.0", + "version": "1.5.0", "license": "MIT", "devDependencies": { "@eslint/js": "^9.22.0", diff --git a/package.json b/package.json index e5827af..e38018c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@makehq/sdk", - "version": "1.4.0", + "version": "1.5.0", "description": "Make TypeScript SDK", "license": "MIT", "author": "Make", diff --git a/src/endpoints/agents.ts b/src/endpoints/agents.ts new file mode 100644 index 0000000..a2ed905 --- /dev/null +++ b/src/endpoints/agents.ts @@ -0,0 +1,206 @@ +import type { FetchFunction } from '../types.js'; + +/** + * Status of an on-prem bridge agent. + */ +export type AgentStatus = 'ACTIVE' | 'STOPPED' | 'NOT_RESPONDING' | 'REGISTERED'; + +/** + * Represents a Make on-prem bridge agent (not Make AI `/v1/agents`). + */ +export type Agent = { + /** Unique identifier of the agent */ + id: string; + /** Tenant identifier in the agency service */ + tenantId: string; + /** User-defined name of the agent */ + name: string; + /** Client secret for agent authentication (sensitive; may only be shown at creation) */ + clientSecret?: string; + /** Current operational status */ + status: AgentStatus; + /** Whether the agent has been alerted */ + alerted: boolean; + /** Whether the agent is currently connected */ + connected: boolean; + /** Installed agent version */ + version?: string; + /** When the agent was created */ + createdDate?: string; + /** Last successful connection timestamp */ + lastConnectionDate?: string; + /** Number of connected systems using this agent */ + systemConnectionsCount?: number; +}; + +/** + * Parameters for registering a new on-prem agent. + */ +export type CreateAgentBody = { + /** Display name for the new agent */ + name: string; +}; + +/** + * Parameters for updating an on-prem agent. + */ +export type UpdateAgentBody = { + /** New name for the agent */ + name?: string; +}; + +/** + * Field definition returned for connected-system configuration on an agent app. + */ +export type AgentAppConfigField = { + /** Field identifier used in `inputs` when creating a connected system */ + name: string; + /** Human-readable label */ + label: string; + /** Help text, if any */ + help?: string | null; + /** Whether the field is required */ + required?: boolean; +}; + +/** + * Forman-style input descriptor for an agent app's connected-system form. + */ +export type AgentAppConfigInput = { + /** Top-level field name (typically `inputs` for a collection) */ + name: string; + /** Human-readable label */ + label: string; + /** Field type (e.g. `collection`) */ + type: string; + /** Nested field definitions when `type` is `collection` */ + spec?: AgentAppConfigField[]; +}; + +type ListAgentsResponse = { + agents: Agent[]; +}; + +type GetAgentResponse = { + agent: Agent; +}; + +type CreateAgentResponse = { + agent: Agent; +}; + +type UpdateAgentResponse = { + agent: Agent; +}; + +type DeleteAgentResponse = { + agent: string; +}; + +type GetAgentAppConfigResponse = { + inputs: AgentAppConfigInput[]; +}; + +/** + * Class providing methods for Make on-prem bridge agents. + * These agents run on customer infrastructure and connect to Make via the agency service. + */ +export class Agents { + readonly #fetch: FetchFunction; + + constructor(fetch: FetchFunction) { + this.#fetch = fetch; + } + + /** + * List on-prem agents for an organization. + * @param organizationId The organization ID + */ + async list(organizationId: number): Promise { + return ( + await this.#fetch('/agents', { + query: { organizationId }, + }) + ).agents; + } + + /** + * Get details of a specific on-prem agent. + * @param organizationId The organization ID + * @param agentId The agent UUID + */ + async get(organizationId: number, agentId: string): Promise { + return ( + await this.#fetch(`/agents/${agentId}`, { + query: { organizationId }, + }) + ).agent; + } + + /** + * Register a new on-prem agent. The server assigns `id` and `clientSecret`. + * @param organizationId The organization ID + * @param body Agent registration parameters (`name` only) + */ + async create(organizationId: number, body: CreateAgentBody): Promise { + return ( + await this.#fetch('/agent/register', { + method: 'POST', + query: { organizationId }, + body, + }) + ).agent; + } + + /** + * Update an on-prem agent (currently supports renaming via `name`). + * @param organizationId The organization ID + * @param agentId The agent UUID + * @param body Fields to update + */ + async update(organizationId: number, agentId: string, body: UpdateAgentBody): Promise { + return ( + await this.#fetch(`/agents/${agentId}`, { + method: 'PATCH', + query: { organizationId }, + body, + }) + ).agent; + } + + /** + * Delete an on-prem agent. + * @param organizationId The organization ID + * @param agentId The agent UUID + * @returns The deleted agent ID + */ + async delete(organizationId: number, agentId: string): Promise { + return ( + await this.#fetch(`/agents/${agentId}`, { + method: 'DELETE', + query: { organizationId }, + }) + ).agent; + } + + /** + * Get dynamic input field definitions for creating a connected system on an agent app. + * @param organizationId The organization ID + * @param agentId The agent UUID + * @param appName Connected-system app slug (e.g. `http`, `sap-agent`) + */ + async getAppConfig( + organizationId: number, + agentId: string, + appName: string, + ): Promise { + return ( + await this.#fetch( + `/agents/${agentId}/apps/${appName}/config`, + { + query: { organizationId }, + }, + ) + ).inputs; + } +} diff --git a/src/endpoints/connected-systems.tools.ts b/src/endpoints/connected-systems.tools.ts new file mode 100644 index 0000000..97c46dc --- /dev/null +++ b/src/endpoints/connected-systems.tools.ts @@ -0,0 +1,205 @@ +import type { Make } from '../make.js'; +import type { JSONValue } from '../types.js'; +import type { MakeTool } from '../tools.js'; + +export const tools: MakeTool[] = [ + { + name: 'connected-system_list', + title: 'List connected systems', + description: + 'List on-prem connected systems for a specific on-prem agent. Requires both organizationId and agentId.', + category: 'connected-system', + scope: 'agents:read', + scopeId: 'organizationId', + identifier: 'organizationId', + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + agentId: { type: 'string', description: 'The on-prem agent UUID' }, + }, + required: ['organizationId', 'agentId'], + }, + examples: [ + { + organizationId: 5, + agentId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + }, + ], + execute: async (make: Make, args: { organizationId: number; agentId: string }) => { + return await make.connectedSystems.list(args.organizationId, args.agentId); + }, + }, + { + name: 'connected-system_get', + title: 'Get connected system', + description: 'Get details of an on-prem connected system.', + category: 'connected-system', + scope: 'agents:read', + scopeId: 'organizationId', + identifier: 'organizationId', + resourceId: 'connectedSystemId', + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + connectedSystemId: { type: 'string', description: 'The connected system UUID' }, + }, + required: ['organizationId', 'connectedSystemId'], + }, + examples: [ + { + organizationId: 5, + connectedSystemId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + }, + ], + execute: async (make: Make, args: { organizationId: number; connectedSystemId: string }) => { + return await make.connectedSystems.get(args.organizationId, args.connectedSystemId); + }, + }, + { + name: 'connected-system_create', + title: 'Create connected system', + description: + 'Create an on-prem connected system on an agent. First use connected-system_list-apps to pick appName, then on-prem-agent_get-app-config to discover required input keys. Supply inputs as a keyed object matching those fields.', + category: 'connected-system', + scope: 'agents:write', + scopeId: 'organizationId', + identifier: 'organizationId', + annotations: { + idempotentHint: false, + destructiveHint: false, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + name: { type: 'string', description: 'Display name for the connected system' }, + agentId: { type: 'string', description: 'On-prem agent UUID that hosts the connection' }, + appName: { type: 'string', description: 'App slug from connected-system_list-apps' }, + inputs: { + type: 'object', + description: 'App-specific configuration values keyed by field name', + }, + }, + required: ['organizationId', 'name', 'agentId', 'appName', 'inputs'], + }, + examples: [ + { + organizationId: 5, + name: 'SAP production', + agentId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + appName: 'sap-agent', + inputs: { client: '100', language: 'EN' }, + }, + ], + execute: async ( + make: Make, + args: { + organizationId: number; + name: string; + agentId: string; + appName: string; + inputs: Record; + }, + ) => { + return await make.connectedSystems.create(args.organizationId, { + name: args.name, + agentId: args.agentId, + appName: args.appName, + inputs: args.inputs, + }); + }, + }, + { + name: 'connected-system_update', + title: 'Update connected system', + description: 'Update an on-prem connected system. All body fields are optional.', + category: 'connected-system', + scope: 'agents:write', + scopeId: 'organizationId', + identifier: 'organizationId', + resourceId: 'connectedSystemId', + annotations: { + idempotentHint: true, + destructiveHint: false, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + connectedSystemId: { type: 'string', description: 'The connected system UUID' }, + name: { type: 'string', description: 'New display name' }, + agentId: { type: 'string', description: 'Move to a different on-prem agent' }, + appName: { type: 'string', description: 'Change the app slug' }, + inputs: { + type: 'object', + description: 'Updated configuration values keyed by field name', + }, + }, + required: ['organizationId', 'connectedSystemId'], + }, + examples: [ + { + organizationId: 5, + connectedSystemId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + name: 'SAP staging', + }, + ], + execute: async ( + make: Make, + args: { + organizationId: number; + connectedSystemId: string; + name?: string; + agentId?: string; + appName?: string; + inputs?: Record; + }, + ) => { + return await make.connectedSystems.update(args.organizationId, args.connectedSystemId, { + name: args.name, + agentId: args.agentId, + appName: args.appName, + inputs: args.inputs, + }); + }, + }, + { + name: 'connected-system_delete', + title: 'Delete connected system', + description: 'Delete an on-prem connected system. This is destructive and cannot be undone.', + category: 'connected-system', + scope: 'agents:write', + scopeId: 'organizationId', + identifier: 'organizationId', + resourceId: 'connectedSystemId', + annotations: { + destructiveHint: true, + idempotentHint: true, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + connectedSystemId: { type: 'string', description: 'The connected system UUID' }, + }, + required: ['organizationId', 'connectedSystemId'], + }, + examples: [ + { + organizationId: 5, + connectedSystemId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + }, + ], + execute: async (make: Make, args: { organizationId: number; connectedSystemId: string }) => { + return await make.connectedSystems.delete(args.organizationId, args.connectedSystemId); + }, + }, +]; diff --git a/src/endpoints/connected-systems.ts b/src/endpoints/connected-systems.ts new file mode 100644 index 0000000..471e12c --- /dev/null +++ b/src/endpoints/connected-systems.ts @@ -0,0 +1,165 @@ +import type { FetchFunction, JSONValue } from '../types.js'; + +/** + * A single valued input on a connected system. + */ +export type ConnectedSystemInput = { + /** Input field name */ + name: string; + /** Input value */ + value: string; +}; + +/** + * Represents a connected system (on-prem bridge connection to an external app). + */ +export type ConnectedSystem = { + /** Unique identifier of the connected system */ + id: string; + /** User-defined name */ + name: string; + /** ID of the on-prem agent hosting this connection */ + agentId: string; + /** Connected-system app slug */ + appName: string; + /** Agent version at time of connection */ + agentVersion?: string; + /** Configured inputs */ + inputs?: ConnectedSystemInput[]; +}; + +/** + * Parameters for creating a connected system. + * `inputs` is a keyed object matching the app's dynamic form (see `Agents.getAppConfig`). + */ +export type CreateConnectedSystemBody = { + /** Display name for the connected system */ + name: string; + /** On-prem agent UUID that will host the connection */ + agentId: string; + /** App slug from `Enums.connectedSystemApps()` */ + appName: string; + /** App-specific configuration values keyed by field name */ + inputs: Record; +}; + +/** + * Parameters for updating a connected system. + */ +export type UpdateConnectedSystemBody = { + /** New display name */ + name?: string; + /** Move to a different agent */ + agentId?: string; + /** Change the app (uncommon after creation) */ + appName?: string; + /** Updated configuration values keyed by field name */ + inputs?: Record; +}; + +type ListConnectedSystemsResponse = { + connectedSystems: ConnectedSystem[]; +}; + +type GetConnectedSystemResponse = { + connectedSystem: ConnectedSystem; +}; + +type CreateConnectedSystemResponse = { + connectedSystem: ConnectedSystem; +}; + +type UpdateConnectedSystemResponse = { + connectedSystem: ConnectedSystem; +}; + +type DeleteConnectedSystemResponse = { + connectedSystem: string; +}; + +/** + * Class providing methods for Make on-prem connected systems. + */ +export class ConnectedSystems { + readonly #fetch: FetchFunction; + + constructor(fetch: FetchFunction) { + this.#fetch = fetch; + } + + /** + * List connected systems for an on-prem agent. + * @param organizationId The organization ID + * @param agentId The on-prem agent UUID + */ + async list(organizationId: number, agentId: string): Promise { + return ( + await this.#fetch('/connected-systems', { + query: { organizationId, agentId }, + }) + ).connectedSystems; + } + + /** + * Get details of a connected system. + * @param organizationId The organization ID + * @param connectedSystemId The connected system UUID + */ + async get(organizationId: number, connectedSystemId: string): Promise { + return ( + await this.#fetch(`/connected-systems/${connectedSystemId}`, { + query: { organizationId }, + }) + ).connectedSystem; + } + + /** + * Create a connected system on an on-prem agent. + * @param organizationId The organization ID + * @param body Creation parameters including app-specific `inputs` + */ + async create(organizationId: number, body: CreateConnectedSystemBody): Promise { + return ( + await this.#fetch('/connected-systems', { + method: 'POST', + query: { organizationId }, + body, + }) + ).connectedSystem; + } + + /** + * Update a connected system. + * @param organizationId The organization ID + * @param connectedSystemId The connected system UUID + * @param body Fields to update + */ + async update( + organizationId: number, + connectedSystemId: string, + body: UpdateConnectedSystemBody, + ): Promise { + return ( + await this.#fetch(`/connected-systems/${connectedSystemId}`, { + method: 'PATCH', + query: { organizationId }, + body, + }) + ).connectedSystem; + } + + /** + * Delete a connected system. + * @param organizationId The organization ID + * @param connectedSystemId The connected system UUID + * @returns The deleted connected system ID + */ + async delete(organizationId: number, connectedSystemId: string): Promise { + return ( + await this.#fetch(`/connected-systems/${connectedSystemId}`, { + method: 'DELETE', + query: { organizationId }, + }) + ).connectedSystem; + } +} diff --git a/src/endpoints/enums.tools.ts b/src/endpoints/enums.tools.ts index 4660599..ed9abc9 100644 --- a/src/endpoints/enums.tools.ts +++ b/src/endpoints/enums.tools.ts @@ -62,4 +62,25 @@ export const tools: MakeTool[] = [ return await make.enums.timezones(); }, }, + { + name: 'connected-system_list-apps', + title: 'List connected-system apps', + description: + 'List available app slugs for on-prem connected systems (e.g. http, sap-agent). Use before creating a connected system.', + category: 'connected-system', + scope: 'agents:read', + scopeId: undefined, + identifier: undefined, + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: {}, + }, + examples: [{}], + execute: async (make: Make) => { + return await make.enums.connectedSystemApps(); + }, + }, ]; diff --git a/src/endpoints/enums.ts b/src/endpoints/enums.ts index 04c83dc..205c4ec 100644 --- a/src/endpoints/enums.ts +++ b/src/endpoints/enums.ts @@ -29,6 +29,18 @@ export type Timezone = { offset: string; }; +/** + * On-prem connected-system app available for bridge agents. + */ +export type ConnectedSystemApp = { + /** App slug used as `appName` when creating a connected system */ + name: string; + /** Human-readable label */ + label: string; + /** Icon identifier or URL */ + icon: string; +}; + type ListCountriesResponse = { /** List of countries */ countries: Country[]; @@ -44,6 +56,11 @@ type ListTimezonesResponse = { timezones: Timezone[]; }; +type ListConnectedSystemAppsResponse = { + /** List of connected-system apps supported on on-prem agents */ + connectedSystemApps: ConnectedSystemApp[]; +}; + /** * Class providing methods for working with Make enums. * Enums provide access to standardized lists of values like countries, regions, and timezones. @@ -82,4 +99,14 @@ export class Enums { async timezones(): Promise { return (await this.#fetch('/enums/timezones')).timezones; } + + /** + * Get list of apps available for on-prem connected systems (e.g. `http`, `sap-agent`). + * @returns Promise with the list of connected-system apps + */ + async connectedSystemApps(): Promise { + return ( + await this.#fetch('/enums/connected-system-apps') + ).connectedSystemApps; + } } diff --git a/src/endpoints/on-prem-agents.tools.ts b/src/endpoints/on-prem-agents.tools.ts new file mode 100644 index 0000000..7a8b3a4 --- /dev/null +++ b/src/endpoints/on-prem-agents.tools.ts @@ -0,0 +1,172 @@ +import type { Make } from '../make.js'; +import type { MakeTool } from '../tools.js'; + +export const tools: MakeTool[] = [ + { + name: 'on-prem-agent_list', + title: 'List on-prem agents', + description: + 'List on-prem bridge agents for an organization. These are customer-hosted agents, not Make AI agents.', + category: 'on-prem-agent', + scope: 'agents:read', + scopeId: 'organizationId', + identifier: 'organizationId', + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + }, + required: ['organizationId'], + }, + examples: [{ organizationId: 5 }], + execute: async (make: Make, args: { organizationId: number }) => { + return await make.agents.list(args.organizationId); + }, + }, + { + name: 'on-prem-agent_get', + title: 'Get on-prem agent', + description: 'Get details of a specific on-prem bridge agent.', + category: 'on-prem-agent', + scope: 'agents:read', + scopeId: 'organizationId', + identifier: 'organizationId', + resourceId: 'agentId', + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + agentId: { type: 'string', description: 'The on-prem agent UUID' }, + }, + required: ['organizationId', 'agentId'], + }, + examples: [{ organizationId: 5, agentId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' }], + execute: async (make: Make, args: { organizationId: number; agentId: string }) => { + return await make.agents.get(args.organizationId, args.agentId); + }, + }, + { + name: 'on-prem-agent_create', + title: 'Register on-prem agent', + description: + 'Register a new on-prem bridge agent. The server assigns id and clientSecret; store the secret securely — it may only be shown once.', + category: 'on-prem-agent', + scope: 'agents:write', + scopeId: 'organizationId', + identifier: 'organizationId', + annotations: { + idempotentHint: false, + destructiveHint: false, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + name: { type: 'string', description: 'Display name for the new on-prem agent' }, + }, + required: ['organizationId', 'name'], + }, + examples: [{ organizationId: 5, name: 'Production bridge' }], + execute: async (make: Make, args: { organizationId: number; name: string }) => { + return await make.agents.create(args.organizationId, { name: args.name }); + }, + }, + { + name: 'on-prem-agent_update', + title: 'Update on-prem agent', + description: 'Update an on-prem bridge agent (currently supports renaming).', + category: 'on-prem-agent', + scope: 'agents:write', + scopeId: 'organizationId', + identifier: 'organizationId', + resourceId: 'agentId', + annotations: { + idempotentHint: true, + destructiveHint: false, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + agentId: { type: 'string', description: 'The on-prem agent UUID' }, + name: { type: 'string', description: 'New display name for the agent' }, + }, + required: ['organizationId', 'agentId'], + }, + examples: [{ organizationId: 5, agentId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', name: 'Renamed agent' }], + execute: async (make: Make, args: { organizationId: number; agentId: string; name?: string }) => { + return await make.agents.update(args.organizationId, args.agentId, { name: args.name }); + }, + }, + { + name: 'on-prem-agent_delete', + title: 'Delete on-prem agent', + description: 'Delete an on-prem bridge agent. This is destructive and cannot be undone.', + category: 'on-prem-agent', + scope: 'agents:write', + scopeId: 'organizationId', + identifier: 'organizationId', + resourceId: 'agentId', + annotations: { + destructiveHint: true, + idempotentHint: true, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + agentId: { type: 'string', description: 'The on-prem agent UUID' }, + }, + required: ['organizationId', 'agentId'], + }, + examples: [{ organizationId: 5, agentId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' }], + execute: async (make: Make, args: { organizationId: number; agentId: string }) => { + return await make.agents.delete(args.organizationId, args.agentId); + }, + }, + { + name: 'on-prem-agent_get-app-config', + title: 'Get on-prem agent app config', + description: + 'Get dynamic input field definitions for creating a connected system on an on-prem agent app. Use connected-system_list-apps to choose appName, then supply keyed inputs when creating a connected system.', + category: 'on-prem-agent', + scope: 'agents:read', + scopeId: 'organizationId', + identifier: 'organizationId', + resourceId: 'agentId', + annotations: { + readOnlyHint: true, + }, + inputSchema: { + type: 'object', + properties: { + organizationId: { type: 'number', description: 'The organization ID' }, + agentId: { type: 'string', description: 'The on-prem agent UUID' }, + appName: { + type: 'string', + description: 'Connected-system app slug (e.g. http, sap-agent)', + }, + }, + required: ['organizationId', 'agentId', 'appName'], + }, + examples: [ + { + organizationId: 5, + agentId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + appName: 'http', + }, + ], + execute: async ( + make: Make, + args: { organizationId: number; agentId: string; appName: string }, + ) => { + return await make.agents.getAppConfig(args.organizationId, args.agentId, args.appName); + }, + }, +]; diff --git a/src/index.ts b/src/index.ts index dff4846..080138d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -62,7 +62,23 @@ export type { CloneDataStructureBody, } from './endpoints/data-structures.js'; export type { Device, DeviceInfo, Devices, ListDevicesOptions } from './endpoints/devices.js'; -export type { Enums, Country, Region, Timezone } from './endpoints/enums.js'; +export type { Enums, Country, Region, Timezone, ConnectedSystemApp } from './endpoints/enums.js'; +export type { + Agent, + Agents, + AgentStatus, + AgentAppConfigField, + AgentAppConfigInput, + CreateAgentBody, + UpdateAgentBody, +} from './endpoints/agents.js'; +export type { + ConnectedSystem, + ConnectedSystems, + ConnectedSystemInput, + CreateConnectedSystemBody, + UpdateConnectedSystemBody, +} from './endpoints/connected-systems.js'; export type { Execution, ExecutionDetail, Executions, ListExecutionsOptions } from './endpoints/executions.js'; export type { Folder, diff --git a/src/make.ts b/src/make.ts index e1209a7..b51ce91 100644 --- a/src/make.ts +++ b/src/make.ts @@ -15,6 +15,8 @@ import { Devices } from './endpoints/devices.js'; import { Functions } from './endpoints/functions.js'; import { Organizations } from './endpoints/organizations.js'; import { Enums } from './endpoints/enums.js'; +import { Agents } from './endpoints/agents.js'; +import { ConnectedSystems } from './endpoints/connected-systems.js'; import { PublicTemplates } from './endpoints/public-templates.js'; import { SDKApps } from './endpoints/sdk/apps.js'; import { SDKModules } from './endpoints/sdk/modules.js'; @@ -164,6 +166,18 @@ export class Make { */ public readonly enums: Enums; + /** + * Access to on-prem bridge agent endpoints + * On-prem agents run on customer infrastructure and connect to Make via the agency service + */ + public readonly agents: Agents; + + /** + * Access to on-prem connected system endpoints + * Connected systems link on-prem agents to external apps (HTTP, SAP, etc.) + */ + public readonly connectedSystems: ConnectedSystems; + /** * Access to public template endpoints. * Public templates are approved, read-only scenario configurations discoverable and usable by any Make user. @@ -254,6 +268,8 @@ export class Make { this.functions = new Functions(this.fetch.bind(this)); this.organizations = new Organizations(this.fetch.bind(this)); this.enums = new Enums(this.fetch.bind(this)); + this.agents = new Agents(this.fetch.bind(this)); + this.connectedSystems = new ConnectedSystems(this.fetch.bind(this)); this.credentialRequests = new CredentialRequests(this.fetch.bind(this)); this.publicTemplates = new PublicTemplates(this.fetch.bind(this)); this.sdk = { diff --git a/src/tools.ts b/src/tools.ts index 01ad724..d247d3a 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -25,6 +25,8 @@ import { tools as FoldersTools } from './endpoints/folders.tools.js'; import { tools as IncompleteExecutionsTools } from './endpoints/incomplete-executions.tools.js'; import { tools as DataStructuresTools } from './endpoints/data-structures.tools.js'; import { tools as EnumsTools } from './endpoints/enums.tools.js'; +import { tools as OnPremAgentsTools } from './endpoints/on-prem-agents.tools.js'; +import { tools as ConnectedSystemsTools } from './endpoints/connected-systems.tools.js'; import { tools as PublicTemplatesTools } from './endpoints/public-templates.tools.js'; /** @@ -214,6 +216,8 @@ export const MakeTools = [ ...TeamsTools, ...OrganizationsTools, ...UsersTools, + ...OnPremAgentsTools, + ...ConnectedSystemsTools, ...EnumsTools, ...PublicTemplatesTools, ] as MakeTool[]; diff --git a/test/agents.spec.ts b/test/agents.spec.ts new file mode 100644 index 0000000..cad50ea --- /dev/null +++ b/test/agents.spec.ts @@ -0,0 +1,87 @@ +import { describe, expect, it } from '@jest/globals'; +import { Make } from '../src/make.js'; +import { mockFetch } from './test.utils.js'; + +import * as agentsListMock from './mocks/agents/list.json'; +import * as agentGetMock from './mocks/agents/get.json'; +import * as agentCreateMock from './mocks/agents/create.json'; +import * as agentUpdateMock from './mocks/agents/update.json'; +import * as agentDeleteMock from './mocks/agents/delete.json'; +import * as agentAppConfigMock from './mocks/agents/app-config.json'; + +const MAKE_API_KEY = 'api-key'; +const MAKE_ZONE = 'make.local'; +const ORGANIZATION_ID = 5; +const AGENT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'; +const APP_NAME = 'sap-agent'; + +describe('Endpoints: Agents (on-prem)', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + it('Should list on-prem agents', async () => { + mockFetch(`GET https://make.local/api/v2/agents?organizationId=${ORGANIZATION_ID}`, agentsListMock); + + const result = await make.agents.list(ORGANIZATION_ID); + expect(result).toStrictEqual(agentsListMock.agents); + }); + + it('Should get an on-prem agent', async () => { + mockFetch( + `GET https://make.local/api/v2/agents/${AGENT_ID}?organizationId=${ORGANIZATION_ID}`, + agentGetMock, + ); + + const result = await make.agents.get(ORGANIZATION_ID, AGENT_ID); + expect(result).toStrictEqual(agentGetMock.agent); + }); + + it('Should register an on-prem agent via /agent/register', async () => { + const body = { name: 'New bridge' }; + + mockFetch( + `POST https://make.local/api/v2/agent/register?organizationId=${ORGANIZATION_ID}`, + agentCreateMock, + req => { + expect(req.body).toStrictEqual(body); + }, + ); + + const result = await make.agents.create(ORGANIZATION_ID, body); + expect(result).toStrictEqual(agentCreateMock.agent); + }); + + it('Should update an on-prem agent', async () => { + const body = { name: 'Renamed bridge' }; + + mockFetch( + `PATCH https://make.local/api/v2/agents/${AGENT_ID}?organizationId=${ORGANIZATION_ID}`, + agentUpdateMock, + req => { + expect(req.body).toStrictEqual(body); + }, + ); + + const result = await make.agents.update(ORGANIZATION_ID, AGENT_ID, body); + expect(result).toStrictEqual(agentUpdateMock.agent); + }); + + it('Should delete an on-prem agent', async () => { + mockFetch( + `DELETE https://make.local/api/v2/agents/${AGENT_ID}?organizationId=${ORGANIZATION_ID}`, + agentDeleteMock, + ); + + const result = await make.agents.delete(ORGANIZATION_ID, AGENT_ID); + expect(result).toStrictEqual(agentDeleteMock.agent); + }); + + it('Should get app config for connected-system inputs', async () => { + mockFetch( + `GET https://make.local/api/v2/agents/${AGENT_ID}/apps/${APP_NAME}/config?organizationId=${ORGANIZATION_ID}`, + agentAppConfigMock, + ); + + const result = await make.agents.getAppConfig(ORGANIZATION_ID, AGENT_ID, APP_NAME); + expect(result).toStrictEqual(agentAppConfigMock.inputs); + }); +}); diff --git a/test/connected-systems.spec.ts b/test/connected-systems.spec.ts new file mode 100644 index 0000000..be21c6d --- /dev/null +++ b/test/connected-systems.spec.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from '@jest/globals'; +import { Make } from '../src/make.js'; +import { mockFetch } from './test.utils.js'; + +import * as connectedSystemsListMock from './mocks/connected-systems/list.json'; +import * as connectedSystemGetMock from './mocks/connected-systems/get.json'; +import * as connectedSystemCreateMock from './mocks/connected-systems/create.json'; +import * as connectedSystemUpdateMock from './mocks/connected-systems/update.json'; +import * as connectedSystemDeleteMock from './mocks/connected-systems/delete.json'; + +const MAKE_API_KEY = 'api-key'; +const MAKE_ZONE = 'make.local'; +const ORGANIZATION_ID = 5; +const AGENT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'; +const CONNECTED_SYSTEM_ID = 'b2c3d4e5-f6a7-8901-bcde-f12345678901'; + +describe('Endpoints: Connected systems (on-prem)', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + it('Should list connected systems for an agent', async () => { + mockFetch( + `GET https://make.local/api/v2/connected-systems?organizationId=${ORGANIZATION_ID}&agentId=${AGENT_ID}`, + connectedSystemsListMock, + ); + + const result = await make.connectedSystems.list(ORGANIZATION_ID, AGENT_ID); + expect(result).toStrictEqual(connectedSystemsListMock.connectedSystems); + }); + + it('Should get a connected system', async () => { + mockFetch( + `GET https://make.local/api/v2/connected-systems/${CONNECTED_SYSTEM_ID}?organizationId=${ORGANIZATION_ID}`, + connectedSystemGetMock, + ); + + const result = await make.connectedSystems.get(ORGANIZATION_ID, CONNECTED_SYSTEM_ID); + expect(result).toStrictEqual(connectedSystemGetMock.connectedSystem); + }); + + it('Should create a connected system with keyed inputs object', async () => { + const body = { + name: 'SAP production', + agentId: AGENT_ID, + appName: 'sap-agent', + inputs: { client: '100', language: 'EN' }, + }; + + mockFetch( + `POST https://make.local/api/v2/connected-systems?organizationId=${ORGANIZATION_ID}`, + connectedSystemCreateMock, + req => { + expect(req.body).toStrictEqual(body); + }, + ); + + const result = await make.connectedSystems.create(ORGANIZATION_ID, body); + expect(result).toStrictEqual(connectedSystemCreateMock.connectedSystem); + }); + + it('Should update a connected system', async () => { + const body = { name: 'SAP staging' }; + + mockFetch( + `PATCH https://make.local/api/v2/connected-systems/${CONNECTED_SYSTEM_ID}?organizationId=${ORGANIZATION_ID}`, + connectedSystemUpdateMock, + req => { + expect(req.body).toStrictEqual(body); + }, + ); + + const result = await make.connectedSystems.update(ORGANIZATION_ID, CONNECTED_SYSTEM_ID, body); + expect(result).toStrictEqual(connectedSystemUpdateMock.connectedSystem); + }); + + it('Should delete a connected system', async () => { + mockFetch( + `DELETE https://make.local/api/v2/connected-systems/${CONNECTED_SYSTEM_ID}?organizationId=${ORGANIZATION_ID}`, + connectedSystemDeleteMock, + ); + + const result = await make.connectedSystems.delete(ORGANIZATION_ID, CONNECTED_SYSTEM_ID); + expect(result).toStrictEqual(connectedSystemDeleteMock.connectedSystem); + }); +}); diff --git a/test/enums.spec.ts b/test/enums.spec.ts index fce176c..a9c8b8c 100644 --- a/test/enums.spec.ts +++ b/test/enums.spec.ts @@ -5,6 +5,7 @@ import { mockFetch } from './test.utils.js'; import * as countriesMock from './mocks/enums/countries.json'; import * as regionsMock from './mocks/enums/regions.json'; import * as timezonesMock from './mocks/enums/timezones.json'; +import * as connectedSystemAppsMock from './mocks/enums/connected-system-apps.json'; const MAKE_API_KEY = 'api-key'; const MAKE_ZONE = 'make.local'; @@ -32,4 +33,11 @@ describe('Endpoints: Enums', () => { const result = await make.enums.timezones(); expect(result).toStrictEqual(timezonesMock.timezones); }); + + it('Should list connected-system apps', async () => { + mockFetch('GET https://make.local/api/v2/enums/connected-system-apps', connectedSystemAppsMock); + + const result = await make.enums.connectedSystemApps(); + expect(result).toStrictEqual(connectedSystemAppsMock.connectedSystemApps); + }); }); diff --git a/test/mcp.spec.ts b/test/mcp.spec.ts index c7db570..b96acaa 100644 --- a/test/mcp.spec.ts +++ b/test/mcp.spec.ts @@ -92,6 +92,30 @@ describe('MCP Tools', () => { expect(coreCategories).toContain('connections'); expect(coreCategories).toContain('teams'); expect(coreCategories).toContain('data-stores'); + expect(coreCategories).toContain('on-prem-agent'); + expect(coreCategories).toContain('connected-system'); + }); + + it('Should expose on-prem agent MCP tools', () => { + const onPremAgentTools = MakeTools.filter(tool => tool.category === 'on-prem-agent'); + const names = onPremAgentTools.map(tool => tool.name); + expect(names).toContain('on-prem-agent_list'); + expect(names).toContain('on-prem-agent_get'); + expect(names).toContain('on-prem-agent_create'); + expect(names).toContain('on-prem-agent_update'); + expect(names).toContain('on-prem-agent_delete'); + expect(names).toContain('on-prem-agent_get-app-config'); + }); + + it('Should expose connected-system MCP tools', () => { + const connectedSystemTools = MakeTools.filter(tool => tool.category === 'connected-system'); + const names = connectedSystemTools.map(tool => tool.name); + expect(names).toContain('connected-system_list'); + expect(names).toContain('connected-system_get'); + expect(names).toContain('connected-system_create'); + expect(names).toContain('connected-system_update'); + expect(names).toContain('connected-system_delete'); + expect(names).toContain('connected-system_list-apps'); }); it('Should have proper naming conventions', () => { diff --git a/test/mocks/agents/app-config.json b/test/mocks/agents/app-config.json new file mode 100644 index 0000000..8f50592 --- /dev/null +++ b/test/mocks/agents/app-config.json @@ -0,0 +1,22 @@ +{ + "inputs": [ + { + "name": "inputs", + "label": "Inputs", + "type": "collection", + "spec": [ + { + "name": "client", + "label": "Client", + "help": "SAP client number", + "required": true + }, + { + "name": "language", + "label": "Language", + "required": false + } + ] + } + ] +} diff --git a/test/mocks/agents/create.json b/test/mocks/agents/create.json new file mode 100644 index 0000000..07cc52e --- /dev/null +++ b/test/mocks/agents/create.json @@ -0,0 +1,11 @@ +{ + "agent": { + "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", + "tenantId": "tenant-1", + "name": "New bridge", + "clientSecret": "secret-shown-once", + "status": "REGISTERED", + "alerted": false, + "connected": false + } +} diff --git a/test/mocks/agents/delete.json b/test/mocks/agents/delete.json new file mode 100644 index 0000000..09098ca --- /dev/null +++ b/test/mocks/agents/delete.json @@ -0,0 +1,3 @@ +{ + "agent": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" +} diff --git a/test/mocks/agents/get.json b/test/mocks/agents/get.json new file mode 100644 index 0000000..426025b --- /dev/null +++ b/test/mocks/agents/get.json @@ -0,0 +1,12 @@ +{ + "agent": { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "tenantId": "tenant-1", + "name": "Production bridge", + "status": "ACTIVE", + "alerted": false, + "connected": true, + "version": "1.2.3", + "systemConnectionsCount": 2 + } +} diff --git a/test/mocks/agents/list.json b/test/mocks/agents/list.json new file mode 100644 index 0000000..ecd7ae0 --- /dev/null +++ b/test/mocks/agents/list.json @@ -0,0 +1,14 @@ +{ + "agents": [ + { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "tenantId": "tenant-1", + "name": "Production bridge", + "status": "ACTIVE", + "alerted": false, + "connected": true, + "version": "1.2.3", + "systemConnectionsCount": 2 + } + ] +} diff --git a/test/mocks/agents/update.json b/test/mocks/agents/update.json new file mode 100644 index 0000000..39d763e --- /dev/null +++ b/test/mocks/agents/update.json @@ -0,0 +1,10 @@ +{ + "agent": { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "tenantId": "tenant-1", + "name": "Renamed bridge", + "status": "ACTIVE", + "alerted": false, + "connected": true + } +} diff --git a/test/mocks/connected-systems/create.json b/test/mocks/connected-systems/create.json new file mode 100644 index 0000000..ff5f624 --- /dev/null +++ b/test/mocks/connected-systems/create.json @@ -0,0 +1,12 @@ +{ + "connectedSystem": { + "id": "d4e5f6a7-b8c9-0123-defa-234567890123", + "name": "SAP production", + "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "appName": "sap-agent", + "inputs": [ + { "name": "client", "value": "100" }, + { "name": "language", "value": "EN" } + ] + } +} diff --git a/test/mocks/connected-systems/delete.json b/test/mocks/connected-systems/delete.json new file mode 100644 index 0000000..2f36e09 --- /dev/null +++ b/test/mocks/connected-systems/delete.json @@ -0,0 +1,3 @@ +{ + "connectedSystem": "b2c3d4e5-f6a7-8901-bcde-f12345678901" +} diff --git a/test/mocks/connected-systems/get.json b/test/mocks/connected-systems/get.json new file mode 100644 index 0000000..5dbd3c6 --- /dev/null +++ b/test/mocks/connected-systems/get.json @@ -0,0 +1,12 @@ +{ + "connectedSystem": { + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "name": "SAP production", + "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "appName": "sap-agent", + "inputs": [ + { "name": "client", "value": "100" }, + { "name": "language", "value": "EN" } + ] + } +} diff --git a/test/mocks/connected-systems/list.json b/test/mocks/connected-systems/list.json new file mode 100644 index 0000000..5e100d5 --- /dev/null +++ b/test/mocks/connected-systems/list.json @@ -0,0 +1,14 @@ +{ + "connectedSystems": [ + { + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "name": "SAP production", + "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "appName": "sap-agent", + "inputs": [ + { "name": "client", "value": "100" }, + { "name": "language", "value": "EN" } + ] + } + ] +} diff --git a/test/mocks/connected-systems/update.json b/test/mocks/connected-systems/update.json new file mode 100644 index 0000000..c562973 --- /dev/null +++ b/test/mocks/connected-systems/update.json @@ -0,0 +1,8 @@ +{ + "connectedSystem": { + "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "name": "SAP staging", + "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "appName": "sap-agent" + } +} diff --git a/test/mocks/enums/connected-system-apps.json b/test/mocks/enums/connected-system-apps.json new file mode 100644 index 0000000..d914e39 --- /dev/null +++ b/test/mocks/enums/connected-system-apps.json @@ -0,0 +1,14 @@ +{ + "connectedSystemApps": [ + { + "name": "http", + "label": "HTTP", + "icon": "http" + }, + { + "name": "sap-agent", + "label": "SAP", + "icon": "sap" + } + ] +} From 8434bb09a9b6d7d52c060c3d34ea2e79fd56de3e Mon Sep 17 00:00:00 2001 From: Ness Li Date: Thu, 4 Jun 2026 21:56:45 +0100 Subject: [PATCH 2/6] test: add on-prem integration tests and fix SAP input fixtures Add agents/connected-systems integration suites with env-driven create flows, getAppConfig preflight validation, and corrected sap-agent fields (ashost, sysnr, client). Extend unit tests for MakeError paths, MCP tool execute smoke tests, and README/.env.example setup docs. Co-authored-by: Cursor --- .env.example | 15 ++ README.md | 13 ++ src/endpoints/connected-systems.tools.ts | 2 +- test/agents.integration.test.ts | 88 ++++++++++ test/agents.spec.ts | 28 +++ test/connected-systems.integration.test.ts | 181 ++++++++++++++++++++ test/connected-systems.spec.ts | 34 +++- test/enums.integration.test.ts | 13 ++ test/mocks/agents/app-config.json | 15 +- test/mocks/connected-systems/create.json | 5 +- test/mocks/connected-systems/get.json | 5 +- test/mocks/connected-systems/list.json | 5 +- test/on-prem-connected-system.utils.spec.ts | 80 +++++++++ test/on-prem-connected-system.utils.ts | 52 ++++++ test/on-prem-tools.spec.ts | 78 +++++++++ test/utils.spec.ts | 8 + 16 files changed, 609 insertions(+), 13 deletions(-) create mode 100644 .env.example create mode 100644 test/agents.integration.test.ts create mode 100644 test/connected-systems.integration.test.ts create mode 100644 test/on-prem-connected-system.utils.spec.ts create mode 100644 test/on-prem-connected-system.utils.ts create mode 100644 test/on-prem-tools.spec.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2fd78ed --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# Copy to .env and fill in real values: +# cp .env.example .env + +# API token with organizations:read, agents:read, and agents:write scopes +MAKE_API_KEY="REPLACE_WITH_YOUR_API_TOKEN" + +# Zone hostname only (no https://), e.g. eu2.make.com or us2.make.com +MAKE_ZONE="eu2.make.com" + +# Numeric organization ID (required for on-prem agent / connected-system tests) +MAKE_ORGANIZATION="12345" + +# Connected-system *create* integration (keys must match getAppConfig for each app) +MAKE_CONNECTED_SYSTEM_HTTP_INPUTS='{"url":"https://example.com"}' +MAKE_CONNECTED_SYSTEM_SAP_AGENT_INPUTS='{"ashost":"00","sysnr":"00","client":"00"}' diff --git a/README.md b/README.md index aebe0e3..017fb3a 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,19 @@ MAKE_TEAM="" MAKE_ORGANIZATION="" ``` +Required for connected-system **create** integration (field names from `getAppConfig`): + +``` +MAKE_CONNECTED_SYSTEM_HTTP_INPUTS='{"url":"https://example.com"}' +MAKE_CONNECTED_SYSTEM_SAP_AGENT_INPUTS='{"ashost":"00","sysnr":"00","client":"00"}' +``` + +Each create suite registers a **new** on-prem agent, then creates the connected system on that agent (no agent ID in `.env`). Use the same input field names and values you would enter in the Make UI. For `sap-agent`, the form uses `ashost`, `sysnr`, and `client` (not `language`); confirm names via `getAppConfig` if your zone differs. + +Read tests always cover **http** and **sap-agent**. Create suites run when the matching `MAKE_CONNECTED_SYSTEM_*_INPUTS` is set. + +Copy `.env.example` to `.env` and fill in values. The organization must have `license.onPremAgent` enabled; the API token needs `organizations:read`, `agents:read`, and `agents:write` scopes. + Please provide zone without `https://` prefix (e.g. `eu2.make.com`). ## Building diff --git a/src/endpoints/connected-systems.tools.ts b/src/endpoints/connected-systems.tools.ts index 97c46dc..ffe8d92 100644 --- a/src/endpoints/connected-systems.tools.ts +++ b/src/endpoints/connected-systems.tools.ts @@ -96,7 +96,7 @@ export const tools: MakeTool[] = [ name: 'SAP production', agentId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', appName: 'sap-agent', - inputs: { client: '100', language: 'EN' }, + inputs: { ashost: '00', sysnr: '00', client: '00' }, }, ], execute: async ( diff --git a/test/agents.integration.test.ts b/test/agents.integration.test.ts new file mode 100644 index 0000000..bcbe162 --- /dev/null +++ b/test/agents.integration.test.ts @@ -0,0 +1,88 @@ +import 'dotenv/config'; +import { afterAll, describe, expect, it } from '@jest/globals'; +import { Make } from '../src/make.js'; +import { MakeTools } from '../src/tools.js'; + +const MAKE_API_KEY = String(process.env.MAKE_API_KEY || ''); +const MAKE_ZONE = String(process.env.MAKE_ZONE || ''); +const MAKE_ORGANIZATION = Number(process.env.MAKE_ORGANIZATION || 0); + +const CONNECTED_SYSTEM_APPS = ['http', 'sap-agent'] as const; + +const integrationReady = Boolean(MAKE_API_KEY && MAKE_ZONE && MAKE_ORGANIZATION); + +/** Valid UUID that is unlikely to exist in the test org */ +const NON_EXISTENT_AGENT_ID = '00000000-0000-4000-8000-000000000000'; + +(integrationReady ? describe : describe.skip)('Integration: Agents (on-prem)', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + let agentId: string; + const agentName = `SDK integration agent ${Date.now()}`; + + it('Should verify on-prem agent license on the organization', async () => { + const org = await make.organizations.get(MAKE_ORGANIZATION); + expect(org.license?.onPremAgent).toBe(true); + }); + + it('Should register an on-prem agent via /agent/register', async () => { + const agent = await make.agents.create(MAKE_ORGANIZATION, { name: agentName }); + + expect(agent.id).toBeDefined(); + expect(agent.name).toBe(agentName); + expect(agent.status).toBeDefined(); + + agentId = agent.id; + }); + + it('Should list on-prem agents', async () => { + const agents = await make.agents.list(MAKE_ORGANIZATION); + + expect(Array.isArray(agents)).toBe(true); + expect(agents.some(a => a.id === agentId)).toBe(true); + }); + + it('Should get an on-prem agent', async () => { + const agent = await make.agents.get(MAKE_ORGANIZATION, agentId); + + expect(agent.id).toBe(agentId); + expect(agent.name).toBe(agentName); + }); + + it('Should update an on-prem agent name', async () => { + const updatedName = `${agentName} updated`; + const agent = await make.agents.update(MAKE_ORGANIZATION, agentId, { name: updatedName }); + + expect(agent.id).toBe(agentId); + expect(agent.name).toBe(updatedName); + }); + + it.each(CONNECTED_SYSTEM_APPS)('Should get app config for %s', async appName => { + const inputs = await make.agents.getAppConfig(MAKE_ORGANIZATION, agentId, appName); + + expect(Array.isArray(inputs)).toBe(true); + expect(inputs.length).toBeGreaterThan(0); + }); + + it('Should execute on-prem-agent_list MCP tool', async () => { + const tool = MakeTools.find(entry => entry.name === 'on-prem-agent_list'); + expect(tool).toBeDefined(); + + const result = await tool!.execute(make, { organizationId: MAKE_ORGANIZATION }); + expect(Array.isArray(result)).toBe(true); + expect((result as { id: string }[]).some(agent => agent.id === agentId)).toBe(true); + }); + + it('Should throw MakeError for a non-existent on-prem agent', async () => { + await expect(make.agents.get(MAKE_ORGANIZATION, NON_EXISTENT_AGENT_ID)).rejects.toMatchObject({ + name: 'MakeError', + statusCode: 400, + }); + }); + + afterAll(async () => { + if (agentId) { + await make.agents.delete(MAKE_ORGANIZATION, agentId); + } + }); +}); diff --git a/test/agents.spec.ts b/test/agents.spec.ts index cad50ea..e7e455d 100644 --- a/test/agents.spec.ts +++ b/test/agents.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from '@jest/globals'; import { Make } from '../src/make.js'; +import { MakeError } from '../src/utils.js'; import { mockFetch } from './test.utils.js'; import * as agentsListMock from './mocks/agents/list.json'; @@ -84,4 +85,31 @@ describe('Endpoints: Agents (on-prem)', () => { const result = await make.agents.getAppConfig(ORGANIZATION_ID, AGENT_ID, APP_NAME); expect(result).toStrictEqual(agentAppConfigMock.inputs); }); + + it('Should throw MakeError when the agent is not found', async () => { + mockFetch( + `GET https://make.local/api/v2/agents/${AGENT_ID}?organizationId=${ORGANIZATION_ID}`, + { message: 'Not found' }, + 404, + ); + + await expect(make.agents.get(ORGANIZATION_ID, AGENT_ID)).rejects.toMatchObject({ + name: 'MakeError', + statusCode: 404, + message: 'Not found', + }); + }); + + it('Should throw MakeError when agent registration fails validation', async () => { + mockFetch( + `POST https://make.local/api/v2/agent/register?organizationId=${ORGANIZATION_ID}`, + { + message: 'Validation failed', + suberrors: [{ message: 'Name is required' }], + }, + 422, + ); + + await expect(make.agents.create(ORGANIZATION_ID, { name: '' })).rejects.toBeInstanceOf(MakeError); + }); }); diff --git a/test/connected-systems.integration.test.ts b/test/connected-systems.integration.test.ts new file mode 100644 index 0000000..af454f2 --- /dev/null +++ b/test/connected-systems.integration.test.ts @@ -0,0 +1,181 @@ +import 'dotenv/config'; +import { afterAll, beforeAll, describe, expect, it } from '@jest/globals'; +import { Make } from '../src/make.js'; +import { MakeTools } from '../src/tools.js'; +import { + assertInputsMatchAppConfig, + parseInputs, +} from './on-prem-connected-system.utils.js'; + +const MAKE_API_KEY = String(process.env.MAKE_API_KEY || ''); +const MAKE_ZONE = String(process.env.MAKE_ZONE || ''); +const MAKE_ORGANIZATION = Number(process.env.MAKE_ORGANIZATION || 0); + +const CONNECTED_SYSTEM_APPS = ['http', 'sap-agent'] as const; + +type ConnectedSystemAppName = (typeof CONNECTED_SYSTEM_APPS)[number]; + +const CREATE_INPUTS_ENV: Record = { + http: 'MAKE_CONNECTED_SYSTEM_HTTP_INPUTS', + 'sap-agent': 'MAKE_CONNECTED_SYSTEM_SAP_AGENT_INPUTS', +}; + +const integrationReady = Boolean(MAKE_API_KEY && MAKE_ZONE && MAKE_ORGANIZATION); + +/** Valid UUID that is unlikely to exist in the test org */ +const NON_EXISTENT_RESOURCE_ID = '00000000-0000-4000-8000-000000000000'; + +function createSuiteEnabled(appName: ConnectedSystemAppName): boolean { + return integrationReady && Boolean(process.env[CREATE_INPUTS_ENV[appName]]); +} + +function getTool(name: string) { + const tool = MakeTools.find(entry => entry.name === name); + if (!tool) { + throw new Error(`Missing MCP tool: ${name}`); + } + return tool; +} + +(integrationReady ? describe : describe.skip)('Integration: Connected systems (on-prem, read)', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + let agentId: string; + + it('Should verify on-prem agent license on the organization', async () => { + const org = await make.organizations.get(MAKE_ORGANIZATION); + expect(org.license?.onPremAgent).toBe(true); + }); + + it('Should provision a temporary on-prem agent for connected-system reads', async () => { + const agent = await make.agents.create(MAKE_ORGANIZATION, { + name: `SDK integration CS agent ${Date.now()}`, + }); + agentId = agent.id; + expect(agentId).toBeDefined(); + }); + + it('Should list http and sap-agent in connected-system apps', async () => { + const apps = await make.enums.connectedSystemApps(); + const names = apps.map(app => app.name); + + expect(Array.isArray(apps)).toBe(true); + for (const appName of CONNECTED_SYSTEM_APPS) { + expect(names).toContain(appName); + } + }); + + it.each(CONNECTED_SYSTEM_APPS)('Should get app config for %s', async appName => { + const config = await make.agents.getAppConfig(MAKE_ORGANIZATION, agentId, appName); + + expect(Array.isArray(config)).toBe(true); + expect(config.length).toBeGreaterThan(0); + }); + + it('Should list connected systems for the agent', async () => { + const systems = await make.connectedSystems.list(MAKE_ORGANIZATION, agentId); + + expect(Array.isArray(systems)).toBe(true); + }); + + it('Should execute connected-system_list MCP tool', async () => { + const tool = getTool('connected-system_list'); + const systems = await tool.execute(make, { + organizationId: MAKE_ORGANIZATION, + agentId, + }); + + expect(Array.isArray(systems)).toBe(true); + }); + + it('Should throw MakeError for a non-existent connected system', async () => { + await expect( + make.connectedSystems.get(MAKE_ORGANIZATION, NON_EXISTENT_RESOURCE_ID), + ).rejects.toMatchObject({ name: 'MakeError', statusCode: 400 }); + }); + + afterAll(async () => { + if (agentId) { + await make.agents.delete(MAKE_ORGANIZATION, agentId); + } + }); +}); + +for (const appName of CONNECTED_SYSTEM_APPS) { + const inputsEnv = CREATE_INPUTS_ENV[appName]; + const inputsRaw = process.env[inputsEnv]; + const suiteReady = createSuiteEnabled(appName); + const skipHint = suiteReady ? '' : ` — set ${inputsEnv} in .env`; + + (suiteReady ? describe : describe.skip)( + `Integration: Connected systems (on-prem, create ${appName})${skipHint}`, + () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + let agentId: string; + let connectedSystemId: string; + const systemName = `SDK integration ${appName} ${Date.now()}`; + + beforeAll(async () => { + const agent = await make.agents.create(MAKE_ORGANIZATION, { + name: `SDK integration ${appName} create ${Date.now()}`, + }); + agentId = agent.id; + + const config = await make.agents.getAppConfig(MAKE_ORGANIZATION, agentId, appName); + assertInputsMatchAppConfig( + config, + parseInputs(inputsRaw, inputsEnv), + inputsEnv, + appName, + ); + }); + + it(`Should create a ${appName} connected system`, async () => { + const connectedSystem = await make.connectedSystems.create(MAKE_ORGANIZATION, { + name: systemName, + agentId, + appName, + inputs: parseInputs(inputsRaw, inputsEnv), + }); + + expect(connectedSystem.id).toBeDefined(); + expect(connectedSystem.name).toBe(systemName); + expect(connectedSystem.agentId).toBe(agentId); + expect(connectedSystem.appName).toBe(appName); + + connectedSystemId = connectedSystem.id; + }); + + it(`Should get the ${appName} connected system`, async () => { + const connectedSystem = await make.connectedSystems.get( + MAKE_ORGANIZATION, + connectedSystemId, + ); + + expect(connectedSystem.id).toBe(connectedSystemId); + expect(connectedSystem.appName).toBe(appName); + }); + + it(`Should update the ${appName} connected system name`, async () => { + const updatedName = `${systemName} updated`; + const connectedSystem = await make.connectedSystems.update( + MAKE_ORGANIZATION, + connectedSystemId, + { name: updatedName }, + ); + + expect(connectedSystem.name).toBe(updatedName); + }); + + afterAll(async () => { + if (connectedSystemId) { + await make.connectedSystems.delete(MAKE_ORGANIZATION, connectedSystemId); + } + if (agentId) { + await make.agents.delete(MAKE_ORGANIZATION, agentId); + } + }); + }, + ); +} diff --git a/test/connected-systems.spec.ts b/test/connected-systems.spec.ts index be21c6d..ed94f3f 100644 --- a/test/connected-systems.spec.ts +++ b/test/connected-systems.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from '@jest/globals'; import { Make } from '../src/make.js'; +import { MakeError } from '../src/utils.js'; import { mockFetch } from './test.utils.js'; import * as connectedSystemsListMock from './mocks/connected-systems/list.json'; @@ -42,7 +43,7 @@ describe('Endpoints: Connected systems (on-prem)', () => { name: 'SAP production', agentId: AGENT_ID, appName: 'sap-agent', - inputs: { client: '100', language: 'EN' }, + inputs: { ashost: '00', sysnr: '00', client: '00' }, }; mockFetch( @@ -81,4 +82,35 @@ describe('Endpoints: Connected systems (on-prem)', () => { const result = await make.connectedSystems.delete(ORGANIZATION_ID, CONNECTED_SYSTEM_ID); expect(result).toStrictEqual(connectedSystemDeleteMock.connectedSystem); }); + + it('Should throw MakeError when the connected system is not found', async () => { + mockFetch( + `GET https://make.local/api/v2/connected-systems/${CONNECTED_SYSTEM_ID}?organizationId=${ORGANIZATION_ID}`, + { message: 'Not found' }, + 404, + ); + + await expect(make.connectedSystems.get(ORGANIZATION_ID, CONNECTED_SYSTEM_ID)).rejects.toMatchObject({ + name: 'MakeError', + statusCode: 404, + message: 'Not found', + }); + }); + + it('Should throw MakeError when agency rejects create', async () => { + const body = { + name: 'SAP production', + agentId: AGENT_ID, + appName: 'sap-agent', + inputs: { language: 'EN' }, + }; + + mockFetch( + `POST https://make.local/api/v2/connected-systems?organizationId=${ORGANIZATION_ID}`, + { message: 'Request to the Agency has failed: [400] Bad Request' }, + 400, + ); + + await expect(make.connectedSystems.create(ORGANIZATION_ID, body)).rejects.toBeInstanceOf(MakeError); + }); }); diff --git a/test/enums.integration.test.ts b/test/enums.integration.test.ts index fe1dd95..1e5afe4 100644 --- a/test/enums.integration.test.ts +++ b/test/enums.integration.test.ts @@ -53,4 +53,17 @@ describe('Integration: Enums', () => { expect(timezone?.code).toBeDefined(); expect(timezone?.offset).toBeDefined(); }); + + it('Should list connected-system apps', async () => { + const apps = await make.enums.connectedSystemApps(); + + expect(apps).toBeDefined(); + expect(Array.isArray(apps)).toBe(true); + expect(apps.length).toBeGreaterThan(0); + + const app = apps[0]; + expect(app?.name).toBeDefined(); + expect(app?.label).toBeDefined(); + expect(app?.icon).toBeDefined(); + }); }); diff --git a/test/mocks/agents/app-config.json b/test/mocks/agents/app-config.json index 8f50592..c59139b 100644 --- a/test/mocks/agents/app-config.json +++ b/test/mocks/agents/app-config.json @@ -5,16 +5,21 @@ "label": "Inputs", "type": "collection", "spec": [ + { + "name": "ashost", + "label": "Application server host", + "required": true + }, + { + "name": "sysnr", + "label": "System number", + "required": true + }, { "name": "client", "label": "Client", "help": "SAP client number", "required": true - }, - { - "name": "language", - "label": "Language", - "required": false } ] } diff --git a/test/mocks/connected-systems/create.json b/test/mocks/connected-systems/create.json index ff5f624..130de28 100644 --- a/test/mocks/connected-systems/create.json +++ b/test/mocks/connected-systems/create.json @@ -5,8 +5,9 @@ "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "appName": "sap-agent", "inputs": [ - { "name": "client", "value": "100" }, - { "name": "language", "value": "EN" } + { "name": "ashost", "value": "00" }, + { "name": "sysnr", "value": "00" }, + { "name": "client", "value": "00" } ] } } diff --git a/test/mocks/connected-systems/get.json b/test/mocks/connected-systems/get.json index 5dbd3c6..34d8538 100644 --- a/test/mocks/connected-systems/get.json +++ b/test/mocks/connected-systems/get.json @@ -5,8 +5,9 @@ "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "appName": "sap-agent", "inputs": [ - { "name": "client", "value": "100" }, - { "name": "language", "value": "EN" } + { "name": "ashost", "value": "00" }, + { "name": "sysnr", "value": "00" }, + { "name": "client", "value": "00" } ] } } diff --git a/test/mocks/connected-systems/list.json b/test/mocks/connected-systems/list.json index 5e100d5..de52c5f 100644 --- a/test/mocks/connected-systems/list.json +++ b/test/mocks/connected-systems/list.json @@ -6,8 +6,9 @@ "agentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "appName": "sap-agent", "inputs": [ - { "name": "client", "value": "100" }, - { "name": "language", "value": "EN" } + { "name": "ashost", "value": "00" }, + { "name": "sysnr", "value": "00" }, + { "name": "client", "value": "00" } ] } ] diff --git a/test/on-prem-connected-system.utils.spec.ts b/test/on-prem-connected-system.utils.spec.ts new file mode 100644 index 0000000..7e7a190 --- /dev/null +++ b/test/on-prem-connected-system.utils.spec.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from '@jest/globals'; +import type { AgentAppConfigInput } from '../src/endpoints/agents.js'; +import { + assertInputsMatchAppConfig, + inputFieldNamesFromAppConfig, + parseInputs, +} from './on-prem-connected-system.utils.js'; + +const SAP_APP_CONFIG: AgentAppConfigInput[] = [ + { + name: 'inputs', + label: 'Inputs', + type: 'collection', + spec: [ + { name: 'ashost', label: 'Host', required: true }, + { name: 'sysnr', label: 'System number', required: true }, + { name: 'client', label: 'Client', required: true }, + ], + }, +]; + +describe('on-prem connected-system integration utils', () => { + describe('parseInputs', () => { + it('Should return empty object when raw is undefined', () => { + expect(parseInputs(undefined, 'MAKE_CONNECTED_SYSTEM_HTTP_INPUTS')).toStrictEqual({}); + }); + + it('Should coerce values to strings', () => { + expect(parseInputs('{"port":8080}', 'ENV')).toStrictEqual({ port: '8080' }); + }); + + it('Should reject non-object JSON', () => { + expect(() => parseInputs('[]', 'ENV')).toThrow('ENV must be a JSON object'); + }); + }); + + describe('inputFieldNamesFromAppConfig', () => { + it('Should extract required and all field names from collection spec', () => { + expect(inputFieldNamesFromAppConfig(SAP_APP_CONFIG)).toStrictEqual({ + required: ['ashost', 'sysnr', 'client'], + all: ['ashost', 'sysnr', 'client'], + }); + }); + }); + + describe('assertInputsMatchAppConfig', () => { + it('Should pass when inputs match the form spec', () => { + expect(() => + assertInputsMatchAppConfig( + SAP_APP_CONFIG, + { ashost: '00', sysnr: '00', client: '00' }, + 'MAKE_CONNECTED_SYSTEM_SAP_AGENT_INPUTS', + 'sap-agent', + ), + ).not.toThrow(); + }); + + it('Should fail when required keys are missing', () => { + expect(() => + assertInputsMatchAppConfig( + SAP_APP_CONFIG, + { client: '00' }, + 'MAKE_CONNECTED_SYSTEM_SAP_AGENT_INPUTS', + 'sap-agent', + ), + ).toThrow(/Missing required keys: ashost, sysnr/); + }); + + it('Should fail when unknown keys are present', () => { + expect(() => + assertInputsMatchAppConfig( + SAP_APP_CONFIG, + { ashost: '00', sysnr: '00', client: '00', language: 'EN' }, + 'MAKE_CONNECTED_SYSTEM_SAP_AGENT_INPUTS', + 'sap-agent', + ), + ).toThrow(/Unknown keys \(not in form spec\): language/); + }); + }); +}); diff --git a/test/on-prem-connected-system.utils.ts b/test/on-prem-connected-system.utils.ts new file mode 100644 index 0000000..4cf0111 --- /dev/null +++ b/test/on-prem-connected-system.utils.ts @@ -0,0 +1,52 @@ +import type { AgentAppConfigInput } from '../src/endpoints/agents.js'; + +export function parseInputs(raw: string | undefined, envName: string): Record { + if (!raw) { + return {}; + } + const parsed: unknown = JSON.parse(raw); + if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { + throw new Error(`${envName} must be a JSON object`); + } + return Object.fromEntries( + Object.entries(parsed as Record).map(([key, value]) => [key, String(value)]), + ); +} + +export function inputFieldNamesFromAppConfig(config: AgentAppConfigInput[]): { + required: string[]; + all: string[]; +} { + const spec = config.find(input => input.name === 'inputs')?.spec ?? []; + + return { + required: spec.filter(field => field.required).map(field => field.name), + all: spec.map(field => field.name), + }; +} + +export function assertInputsMatchAppConfig( + config: AgentAppConfigInput[], + inputs: Record, + envName: string, + appName: string, +): void { + const { required, all } = inputFieldNamesFromAppConfig(config); + const provided = Object.keys(inputs); + const missing = required.filter(name => !provided.includes(name)); + const unknown = provided.filter(name => !all.includes(name)); + + if (missing.length > 0 || unknown.length > 0) { + const parts: string[] = [ + `${envName} does not match getAppConfig for ${appName} on this agent.`, + ]; + if (missing.length > 0) { + parts.push(`Missing required keys: ${missing.join(', ')}.`); + } + if (unknown.length > 0) { + parts.push(`Unknown keys (not in form spec): ${unknown.join(', ')}.`); + } + parts.push(`Expected field names: ${all.join(', ') || '(none)'}.`); + throw new Error(parts.join(' ')); + } +} diff --git a/test/on-prem-tools.spec.ts b/test/on-prem-tools.spec.ts new file mode 100644 index 0000000..d598233 --- /dev/null +++ b/test/on-prem-tools.spec.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from '@jest/globals'; +import { Make } from '../src/make.js'; +import { MakeTools } from '../src/tools.js'; +import { mockFetch } from './test.utils.js'; + +import * as agentsListMock from './mocks/agents/list.json'; +import * as agentAppConfigMock from './mocks/agents/app-config.json'; +import * as connectedSystemAppsMock from './mocks/enums/connected-system-apps.json'; +import * as connectedSystemsListMock from './mocks/connected-systems/list.json'; + +const MAKE_API_KEY = 'api-key'; +const MAKE_ZONE = 'make.local'; +const ORGANIZATION_ID = 5; +const AGENT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'; + +function getTool(name: string) { + const tool = MakeTools.find(entry => entry.name === name); + if (!tool) { + throw new Error(`Missing MCP tool: ${name}`); + } + return tool; +} + +describe('MCP tools: on-prem agent and connected-system', () => { + const make = new Make(MAKE_API_KEY, MAKE_ZONE); + + it('Should execute on-prem-agent_list', async () => { + mockFetch(`GET https://make.local/api/v2/agents?organizationId=${ORGANIZATION_ID}`, agentsListMock); + + const tool = getTool('on-prem-agent_list'); + const result = await tool.execute(make, { organizationId: ORGANIZATION_ID }); + + expect(result).toStrictEqual(agentsListMock.agents); + }); + + it('Should execute on-prem-agent_get-app-config', async () => { + mockFetch( + `GET https://make.local/api/v2/agents/${AGENT_ID}/apps/sap-agent/config?organizationId=${ORGANIZATION_ID}`, + agentAppConfigMock, + ); + + const tool = getTool('on-prem-agent_get-app-config'); + const result = await tool.execute(make, { + organizationId: ORGANIZATION_ID, + agentId: AGENT_ID, + appName: 'sap-agent', + }); + + expect(result).toStrictEqual(agentAppConfigMock.inputs); + }); + + it('Should execute connected-system_list-apps', async () => { + mockFetch( + 'GET https://make.local/api/v2/enums/connected-system-apps', + connectedSystemAppsMock, + ); + + const tool = getTool('connected-system_list-apps'); + const result = await tool.execute(make, {}); + + expect(result).toStrictEqual(connectedSystemAppsMock.connectedSystemApps); + }); + + it('Should execute connected-system_list', async () => { + mockFetch( + `GET https://make.local/api/v2/connected-systems?organizationId=${ORGANIZATION_ID}&agentId=${AGENT_ID}`, + connectedSystemsListMock, + ); + + const tool = getTool('connected-system_list'); + const result = await tool.execute(make, { + organizationId: ORGANIZATION_ID, + agentId: AGENT_ID, + }); + + expect(result).toStrictEqual(connectedSystemsListMock.connectedSystems); + }); +}); diff --git a/test/utils.spec.ts b/test/utils.spec.ts index 360ada2..5d7f245 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from '@jest/globals'; +import type { QueryValue } from '../src/types.js'; import { buildUrl, createMakeError } from '../src/utils.js'; describe('Utils', () => { @@ -19,6 +20,13 @@ describe('Utils', () => { ); }); + it('Should skip null and undefined array elements', () => { + const params = { tags: ['a', null, undefined, 'b'] } as Record; + expect(buildUrl('https://example.com', params)).toBe( + 'https://example.com?tags%5B%5D=a&tags%5B%5D=b', + ); + }); + it('Should build URL with nested parameters', () => { expect( buildUrl('https://example.com', { From e242d782dc4024e0e82c0b3154df692e7f1865b1 Mon Sep 17 00:00:00 2001 From: Ness Li Date: Thu, 4 Jun 2026 22:20:23 +0100 Subject: [PATCH 3/6] chore: refine SAP MCP examples and drop pack tarball from gitignore Use realistic sap-agent input examples in connected-system create/update tools. Remove makehq-sdk-*.tgz from .gitignore (local pack files are not tracked). Co-authored-by: Cursor --- .gitignore | 3 +-- src/endpoints/connected-systems.tools.ts | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e082f38..77652cd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,4 @@ coverage .env node_modules/* dist -docs -makehq-sdk-*.tgz \ No newline at end of file +docs \ No newline at end of file diff --git a/src/endpoints/connected-systems.tools.ts b/src/endpoints/connected-systems.tools.ts index ffe8d92..dc048e3 100644 --- a/src/endpoints/connected-systems.tools.ts +++ b/src/endpoints/connected-systems.tools.ts @@ -96,7 +96,7 @@ export const tools: MakeTool[] = [ name: 'SAP production', agentId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', appName: 'sap-agent', - inputs: { ashost: '00', sysnr: '00', client: '00' }, + inputs: { ashost: '10.0.0.150', sysnr: '00', client: '100' }, }, ], execute: async ( @@ -150,6 +150,7 @@ export const tools: MakeTool[] = [ organizationId: 5, connectedSystemId: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', name: 'SAP staging', + inputs: { ashost: '10.0.0.150', sysnr: '00', client: '100' }, }, ], execute: async ( From b927f58402287d13a5e4b79733e9d451125b785d Mon Sep 17 00:00:00 2001 From: Ness Li Date: Thu, 4 Jun 2026 22:24:53 +0100 Subject: [PATCH 4/6] chore: sync 1.5.0 version to jsr.json and version.ts Run build:version so published artifacts and JSR metadata match package.json. Co-authored-by: Cursor --- jsr.json | 15 +++++++++++---- src/version.ts | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/jsr.json b/jsr.json index 992fa43..2c7e5cb 100644 --- a/jsr.json +++ b/jsr.json @@ -1,10 +1,17 @@ { "name": "@make/sdk", - "version": "0.0.0", + "version": "1.5.0", "exports": "./src/index.ts", "license": "MIT", "publish": { - "include": ["LICENSE", "README.md", "src/**/*.ts"], - "exclude": ["test/*", "scripts/*"] + "include": [ + "LICENSE", + "README.md", + "src/**/*.ts" + ], + "exclude": [ + "test/*", + "scripts/*" + ] } -} +} \ No newline at end of file diff --git a/src/version.ts b/src/version.ts index 6dabcc5..e10d487 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = 'development'; // To be replaced while publishing +export const VERSION = '1.5.0'; \ No newline at end of file From 15cec46df654f7652f7f591f0a61497425353409 Mon Sep 17 00:00:00 2001 From: Ness Li Date: Thu, 4 Jun 2026 22:26:40 +0100 Subject: [PATCH 5/6] test: assert user-agent against VERSION constant in make.spec After syncing version.ts to 1.5.0, stop hardcoding development in the internals override test. Co-authored-by: Cursor --- test/make.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/make.spec.ts b/test/make.spec.ts index 78e6409..2f88db3 100644 --- a/test/make.spec.ts +++ b/test/make.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from '@jest/globals'; import { Make } from '../src/make.js'; +import { VERSION } from '../src/version.js'; import { mockFetch, TestableMake } from './test.utils.js'; import { randomUUID } from 'node:crypto'; import type { QueryValue } from '../src/types.js'; @@ -79,7 +80,7 @@ describe('Make SDK', () => { expect(url).toBe('/users/me'); expect(options?.headers).toStrictEqual({ authorization: `Token ${MAKE_API_KEY}`, - 'user-agent': 'MakeTypeScriptSDK/development', + 'user-agent': `MakeTypeScriptSDK/${VERSION}`, 'x-custom-header': 'test', }); return new Response('{"authUser": {"id": 1}}', { From 96c09752e6283e7ec154072e61826eccd6484612 Mon Sep 17 00:00:00 2001 From: Ness Li Date: Fri, 5 Jun 2026 12:48:17 +0100 Subject: [PATCH 6/6] test: avoid unsupported null query array fixture Keep buildUrl coverage aligned with the public QueryValue type by testing undefined array entries without casting null into the API shape. Co-authored-by: Cursor --- test/utils.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/utils.spec.ts b/test/utils.spec.ts index 5d7f245..80a0c49 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -1,5 +1,4 @@ import { describe, expect, it } from '@jest/globals'; -import type { QueryValue } from '../src/types.js'; import { buildUrl, createMakeError } from '../src/utils.js'; describe('Utils', () => { @@ -20,9 +19,8 @@ describe('Utils', () => { ); }); - it('Should skip null and undefined array elements', () => { - const params = { tags: ['a', null, undefined, 'b'] } as Record; - expect(buildUrl('https://example.com', params)).toBe( + it('Should skip undefined array elements', () => { + expect(buildUrl('https://example.com', { tags: ['a', undefined, 'b'] })).toBe( 'https://example.com?tags%5B%5D=a&tags%5B%5D=b', ); });