-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathscapi-custom-apis-get-status.ts
More file actions
207 lines (186 loc) · 8.26 KB
/
scapi-custom-apis-get-status.ts
File metadata and controls
207 lines (186 loc) · 8.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/*
* Copyright (c) 2025, Salesforce, Inc.
* SPDX-License-Identifier: Apache-2
* For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
*/
/**
* SCAPI Custom API Status tool.
*
* Mirrors CLI: b2c scapi custom status. All CLI flags are supported; let the agent decide what to use.
* Returns raw endpoints from the API (no roll-up). Remote only.
*
* @module tools/scapi/scapi-custom-apis-get-status
*/
import {z} from 'zod';
import {createToolAdapter, jsonResult} from '../adapter.js';
import type {Services} from '../../services.js';
import type {McpTool} from '../../utils/index.js';
import type {CustomApisComponents} from '@salesforce/b2c-tooling-sdk/clients';
import {getApiErrorMessage} from '@salesforce/b2c-tooling-sdk/clients';
import {getApiType} from '@salesforce/b2c-tooling-sdk/schemas';
type CustomApiEndpoint = CustomApisComponents['schemas']['CustomApiEndpoint'];
/** Endpoint with optional display field (type) added by the tool. */
type EndpointWithMeta = CustomApiEndpoint & {type?: string};
// MCP-specific default columns (includes siteId since MCP returns raw endpoints per site)
const DEFAULT_COLUMNS = ['type', 'apiName', 'cartridgeName', 'endpointPath', 'httpMethod', 'status', 'siteId'] as const;
function pickColumns(endpoint: EndpointWithMeta, columns: string[]): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const key of columns) {
if (key in endpoint && (endpoint as Record<string, unknown>)[key] !== undefined) {
out[key] = (endpoint as Record<string, unknown>)[key];
}
}
return out;
}
function buildColumnList(args: CustomListInput): string[] {
// If columns specified, use those; otherwise use defaults (saves tokens)
if (args.columns?.trim()) {
return args.columns
.split(',')
.map((c) => c.trim())
.filter(Boolean);
}
return [...DEFAULT_COLUMNS];
}
function buildResponse(
withMeta: EndpointWithMeta[],
args: CustomListInput,
columnList: string[],
activeCodeVersion: string | undefined,
): CustomListOutput {
// Always filter to requested columns (saves tokens by not returning unused fields)
const toOutput = (e: EndpointWithMeta): Record<string, unknown> =>
pickColumns(e, columnList) as Record<string, unknown>;
if (args.groupBy) {
const groups: Record<string, (EndpointWithMeta | Record<string, unknown>)[]> = {};
if (args.groupBy === 'type') {
for (const endpoint of withMeta) {
const type = endpoint.type ?? '-';
if (!groups[type]) groups[type] = [];
groups[type].push(toOutput(endpoint));
}
} else {
for (const endpoint of withMeta) {
const site = endpoint.siteId ?? 'Global';
if (!groups[site]) groups[site] = [];
groups[site].push(toOutput(endpoint));
}
}
return {
groups,
total: withMeta.length,
activeCodeVersion,
timestamp: new Date().toISOString(),
message: withMeta.length === 0 ? 'No Custom API endpoints found.' : `Found ${withMeta.length} endpoint(s).`,
};
}
return {
endpoints: withMeta.map((e) => toOutput(e)),
total: withMeta.length,
activeCodeVersion,
timestamp: new Date().toISOString(),
message: withMeta.length === 0 ? 'No Custom API endpoints found.' : `Found ${withMeta.length} endpoint(s).`,
};
}
/**
* Input schema for scapi_custom_apis_get_status tool.
* Mirrors b2c scapi custom status (--status, --group-by, --columns).
* Use columns parameter to request all fields or specific fields.
*/
interface CustomListInput {
/** Filter by endpoint status. Same as CLI --status / -s */
status?: 'active' | 'not_registered';
/** Group output by "site" or "type" (Admin/Shopper). Same as CLI --group-by / -g */
groupBy?: 'site' | 'type';
/** Comma-separated columns to include. Same as CLI --columns / -c. Omit for defaults (7 fields). Use all field names for complete data. */
columns?: string;
}
/**
* Output schema for scapi_custom_apis_get_status tool.
*/
interface CustomListOutput {
/** Raw endpoints (one per site). When groupBy is set, use "groups" instead. */
endpoints?: EndpointWithMeta[] | Record<string, unknown>[];
/** When groupBy is set: groups keyed by type ("Admin","Shopper") or by siteId */
groups?: Record<string, (EndpointWithMeta | Record<string, unknown>)[]>;
total: number;
activeCodeVersion?: string;
remoteError?: string;
timestamp: string;
message?: string;
}
/**
* Creates the scapi_custom_apis_get_status tool.
*
* Mirrors CLI: b2c scapi custom status. All flags supported; agent chooses what to use.
* See: https://salesforcecommercecloud.github.io/b2c-developer-tooling/cli/custom-apis.html#b2c-scapi-custom-status
*/
export function createScapiCustomApisStatusTool(loadServices: () => Promise<Services> | Services): McpTool {
return createToolAdapter<CustomListInput, CustomListOutput>(
{
name: 'scapi_custom_apis_get_status',
description: `List Custom SCAPI endpoint registration status (active/not_registered). Returns one row per endpoint per site. For schemas, use scapi_schemas_list with apiFamily: "custom".
Use cases: Check endpoint status, verify deployment, get per-site details. Use status: "active" to filter, groupBy: "site" to group, columns: "field1,field2" for specific fields, or omit columns for defaults.
Output: Default (7 fields): type,apiName,cartridgeName,endpointPath,httpMethod,status,siteId. All fields: type,apiName,apiVersion,cartridgeName,endpointPath,httpMethod,status,siteId,securityScheme,operationId,schemaFile,implementationScript,errorReason,id.
Requires OAuth (sfcc.custom-apis scope) and instance config (shortCode, tenantId). Returns remoteError on failure.
CLI: b2c scapi custom status`,
toolsets: ['PWAV3', 'SCAPI', 'STOREFRONTNEXT'],
isGA: true,
requiresInstance: false,
inputSchema: {
status: z.enum(['active', 'not_registered']).optional().describe('Filter by status. Omit for all.'),
groupBy: z.enum(['site', 'type']).optional().describe('Group by siteId or type (Admin/Shopper).'),
columns: z
.string()
.optional()
.describe(
'Comma-separated fields. Omit for defaults (7 fields). All fields: type,apiName,apiVersion,cartridgeName,endpointPath,httpMethod,status,siteId,securityScheme,operationId,schemaFile,implementationScript,errorReason,id',
),
},
async execute(args, {services: svc}) {
let endpoints: CustomApiEndpoint[] = [];
let activeCodeVersion: string | undefined;
let remoteError: string | undefined;
try {
const client = svc.getCustomApisClient();
const organizationId = svc.getOrganizationId();
// Call Custom APIs DX API: list endpoints for this org, optional status filter.
const {data, error, response} = await client.GET('/organizations/{organizationId}/endpoints', {
params: {
path: {organizationId},
query: args.status ? {status: args.status} : undefined,
},
});
if (error) {
remoteError = `Failed to fetch remote endpoints: ${getApiErrorMessage(error, response)}`;
} else {
endpoints = data?.data ?? [];
activeCodeVersion = data?.activeCodeVersion;
}
} catch (error) {
// Network/config errors: capture message for remoteError and return below.
remoteError = error instanceof Error ? error.message : 'Unknown error fetching remote endpoints';
}
// On any remote failure, return early with error details (no endpoints).
if (remoteError) {
return {
total: 0,
activeCodeVersion,
remoteError,
timestamp: new Date().toISOString(),
message: `Failed to fetch Custom API endpoints: ${remoteError}. Check OAuth credentials and sfcc.custom-apis scope.`,
};
}
// Add type to each endpoint (no roll-up)
const withMeta: EndpointWithMeta[] = endpoints.map((e) => ({
...e,
type: getApiType(e.securityScheme),
}));
const columnList = buildColumnList(args);
return buildResponse(withMeta, args, columnList, activeCodeVersion);
},
formatOutput: (output) => jsonResult(output),
},
loadServices,
);
}