@sema-agent/client-core 0.111.0 → 0.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -0
- package/README.md +15 -6
- package/dist/agentSession/engineSessionBackgroundStop.js +2 -2
- package/dist/checkpointRowVerdict.d.ts +3 -0
- package/dist/checkpointRowVerdict.js +14 -1
- package/dist/consoleCaPin.d.ts +136 -0
- package/dist/consoleCaPin.js +827 -0
- package/dist/decideReceipt.d.ts +2 -1
- package/dist/deviceRebind.js +3 -3
- package/dist/effectiveFacts.d.ts +9 -0
- package/dist/effectiveFacts.js +33 -0
- package/dist/engineAddress.d.ts +1 -0
- package/dist/engineAddress.js +4 -1
- package/dist/engineCredentialRelease.d.ts +54 -0
- package/dist/engineCredentialRelease.js +146 -0
- package/dist/engineErrorCodes.d.ts +1 -0
- package/dist/engineErrorCodes.js +1 -0
- package/dist/engineHealthFacts.d.ts +0 -11
- package/dist/engineHealthFacts.js +0 -21
- package/dist/engineHealthProbeVerdict.d.ts +1 -0
- package/dist/engineHealthProbeVerdict.js +21 -13
- package/dist/engineUnavailable.d.ts +3 -2
- package/dist/engineUnavailable.js +16 -12
- package/dist/engineUrl.js +4 -2
- package/dist/gateFailureCodes.js +2 -2
- package/dist/generated/sdkFactTables.d.ts +1 -0
- package/dist/generated/sdkFactTables.js +13 -0
- package/dist/generated/settingsSchemaFns.d.ts +1 -0
- package/dist/generated/settingsSchemaFns.js +3 -0
- package/dist/generated/settingsSchemaHooksClassifier.js +38 -9
- package/dist/generated/settingsSchemaTables.d.ts +52 -0
- package/dist/generated/settingsSchemaTables.js +18 -0
- package/dist/generated/settingsSchemaTypes.d.ts +23 -13
- package/dist/generated/settingsSchemaUrlShape.d.ts +1 -0
- package/dist/generated/settingsSchemaUrlShape.js +17 -0
- package/dist/hitl/decisionRefusal.d.ts +4 -0
- package/dist/hitl/decisionRefusal.js +12 -2
- package/dist/hitl/localAllowRedemption.d.ts +9 -6
- package/dist/hitl/localAllowRedemption.js +20 -0
- package/dist/hitl/parkOwnership.js +2 -1
- package/dist/hitl/parkResolver.js +7 -0
- package/dist/hitl/screenDecisionCards.js +3 -2
- package/dist/hitl/sessionPolicyWire.js +2 -2
- package/dist/hitl/standingRows.d.ts +4 -2
- package/dist/hitl/standingRows.js +10 -3
- package/dist/hooksReaderGenerations.js +1 -1
- package/dist/i18n/messages.en.d.ts +163 -0
- package/dist/i18n/messages.en.js +163 -0
- package/dist/i18n/messages.zh.js +174 -0
- package/dist/index.d.ts +30 -5
- package/dist/index.js +15 -2
- package/dist/liveModelCatalog.d.ts +4 -0
- package/dist/liveModelCatalog.js +10 -1
- package/dist/localConfig/documents.d.ts +61 -0
- package/dist/localConfig/documents.js +51 -0
- package/dist/localConfig/kernel.d.ts +149 -0
- package/dist/localConfig/kernel.js +597 -0
- package/dist/localConfig/mcp/edit.d.ts +161 -0
- package/dist/localConfig/mcp/edit.js +846 -0
- package/dist/localConfig/mcp/servers.d.ts +165 -0
- package/dist/localConfig/mcp/servers.js +776 -0
- package/dist/localConfig/mcp/views.d.ts +170 -0
- package/dist/localConfig/mcp/views.js +429 -0
- package/dist/localConfig/modelPool.d.ts +262 -0
- package/dist/localConfig/modelPool.js +1489 -0
- package/dist/localConfig/modelPoolWords.d.ts +13 -0
- package/dist/localConfig/modelPoolWords.js +20 -0
- package/dist/localConfig/receiptShape.d.ts +7 -0
- package/dist/localConfig/receiptShape.js +135 -0
- package/dist/localConfig/settingsWrite.d.ts +127 -0
- package/dist/localConfig/settingsWrite.js +237 -0
- package/dist/localConfig/skills.d.ts +124 -0
- package/dist/localConfig/skills.js +397 -0
- package/dist/localConfig/skillsWords.d.ts +10 -0
- package/dist/localConfig/skillsWords.js +5 -0
- package/dist/mcpProbeWire.js +2 -1
- package/dist/mcpWireCaps.js +1 -1
- package/dist/memoryVerbsWire.js +3 -2
- package/dist/model/onboardJudgement.d.ts +14 -4
- package/dist/model/onboardJudgement.js +91 -139
- package/dist/modelConfigGapNotice.d.ts +20 -0
- package/dist/modelConfigGapNotice.js +102 -0
- package/dist/modsKernelIdentity.js +2 -47
- package/dist/ownKey.d.ts +1 -0
- package/dist/ownKey.js +40 -8
- package/dist/permissionsDeclaration.d.ts +2 -1
- package/dist/permissionsDeclaration.js +31 -17
- package/dist/promptBlockedCopy.js +2 -2
- package/dist/resumeRefusalCopy.d.ts +3 -1
- package/dist/resumeRefusalCopy.js +12 -4
- package/dist/runLedgerSnapshot.js +3 -3
- package/dist/runPermissionModeSwitch.js +2 -3
- package/dist/seatContract.d.ts +154 -4
- package/dist/seatContract.js +228 -2
- package/dist/sessionIdSegment.d.ts +1 -0
- package/dist/sessionIdSegment.js +4 -1
- package/dist/sessionMemoryErase.js +2 -1
- package/dist/sha256Bytes.d.ts +1 -0
- package/dist/sha256Bytes.js +46 -0
- package/dist/skillOverrideChain.d.ts +83 -0
- package/dist/skillOverrideChain.js +378 -0
- package/dist/skillsWireCaps.d.ts +3 -0
- package/dist/skillsWireCaps.js +16 -1
- package/dist/turnFailurePresentation.js +4 -10
- package/docs/INTEGRATION-CLIENTS.md +614 -14
- package/package.json +3 -3
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { type McpProbeFacade } from '../../mcpProbeWire.js';
|
|
2
|
+
import type { McpProbeReading } from '../../mcpProbeCapability.js';
|
|
3
|
+
import type { McpAuthorizationView } from '../../mcpLiveness.js';
|
|
4
|
+
import type { UiLanguage } from '../../env/uiLanguage.js';
|
|
5
|
+
import { type LocalConfigPreview, type LocalConfigReceipt, type LocalConfigStorePort } from '../kernel.js';
|
|
6
|
+
import { type McpServersReport } from './servers.js';
|
|
7
|
+
import { type McpServerEdit, type McpServerEditContext, type McpServerEditOutcome } from './edit.js';
|
|
8
|
+
export type McpArgView = {
|
|
9
|
+
readonly kind: 'text';
|
|
10
|
+
readonly text: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: 'masked';
|
|
13
|
+
};
|
|
14
|
+
export interface McpSecretMemberView {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly hasValue: boolean;
|
|
17
|
+
}
|
|
18
|
+
export type McpServerEntryView = {
|
|
19
|
+
readonly transport: 'stdio';
|
|
20
|
+
readonly command: string;
|
|
21
|
+
readonly args: readonly McpArgView[];
|
|
22
|
+
readonly env: readonly McpSecretMemberView[];
|
|
23
|
+
readonly allowTools: readonly string[] | 'invalid' | null;
|
|
24
|
+
} | {
|
|
25
|
+
readonly transport: 'http';
|
|
26
|
+
readonly url: string;
|
|
27
|
+
readonly headers: readonly McpSecretMemberView[];
|
|
28
|
+
readonly headersHelper: readonly McpArgView[] | null;
|
|
29
|
+
readonly oauth: {
|
|
30
|
+
readonly clientId: string | null;
|
|
31
|
+
readonly callbackPort: number | null;
|
|
32
|
+
} | null;
|
|
33
|
+
readonly allowTools: readonly string[] | 'invalid' | null;
|
|
34
|
+
} | {
|
|
35
|
+
readonly transport: 'unsupported';
|
|
36
|
+
readonly declaredType: string;
|
|
37
|
+
} | {
|
|
38
|
+
readonly transport: 'unreadable';
|
|
39
|
+
};
|
|
40
|
+
export interface McpServerProblemView {
|
|
41
|
+
readonly reason: string;
|
|
42
|
+
readonly path: string | null;
|
|
43
|
+
readonly sentence: string;
|
|
44
|
+
}
|
|
45
|
+
export interface McpServerRowView {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly label: string;
|
|
48
|
+
readonly scope: string;
|
|
49
|
+
readonly shadows: readonly string[];
|
|
50
|
+
readonly state: string;
|
|
51
|
+
readonly withheldBy: string | null;
|
|
52
|
+
readonly approvedBy: string | null;
|
|
53
|
+
readonly writable: boolean;
|
|
54
|
+
readonly base: string | null;
|
|
55
|
+
readonly deploymentWins: boolean;
|
|
56
|
+
readonly missing: readonly string[];
|
|
57
|
+
readonly entry: McpServerEntryView;
|
|
58
|
+
readonly problems: readonly McpServerProblemView[];
|
|
59
|
+
readonly sentence: string | null;
|
|
60
|
+
}
|
|
61
|
+
export interface McpServerReportView {
|
|
62
|
+
readonly kind: 'mcp_servers';
|
|
63
|
+
readonly rows: readonly McpServerRowView[];
|
|
64
|
+
readonly unreadable: readonly string[];
|
|
65
|
+
readonly locked: 'enterprise_config' | 'plugin_only' | null;
|
|
66
|
+
readonly purpose: 'report' | 'load';
|
|
67
|
+
readonly approvalsWithheld: readonly McpApprovalWithheldView[];
|
|
68
|
+
readonly deployment: {
|
|
69
|
+
readonly state: 'unknown';
|
|
70
|
+
readonly names: readonly [];
|
|
71
|
+
} | {
|
|
72
|
+
readonly state: 'read';
|
|
73
|
+
readonly names: readonly {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly label: string;
|
|
76
|
+
}[];
|
|
77
|
+
};
|
|
78
|
+
readonly pluginRoster: 'host_supplied' | 'absent';
|
|
79
|
+
readonly promptsAndResources: 'not_yet';
|
|
80
|
+
}
|
|
81
|
+
export interface McpApprovalWithheldView {
|
|
82
|
+
readonly layer: string;
|
|
83
|
+
readonly cause: 'trust_withheld';
|
|
84
|
+
readonly sentence: string;
|
|
85
|
+
}
|
|
86
|
+
export interface McpImportedView {
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly label: string;
|
|
89
|
+
readonly renamedFrom: string | null;
|
|
90
|
+
}
|
|
91
|
+
export interface McpSkippedView {
|
|
92
|
+
readonly name: string;
|
|
93
|
+
readonly label: string;
|
|
94
|
+
readonly reason: string;
|
|
95
|
+
}
|
|
96
|
+
export interface McpProbeServerRowView {
|
|
97
|
+
readonly name: string;
|
|
98
|
+
readonly label: string;
|
|
99
|
+
readonly status: string;
|
|
100
|
+
readonly errorCode: string | null;
|
|
101
|
+
readonly toolCount: number | null;
|
|
102
|
+
readonly authorization: McpAuthorizationView | 'unreadable' | null;
|
|
103
|
+
}
|
|
104
|
+
export interface McpServerRefusalDetailView {
|
|
105
|
+
readonly refusal: string;
|
|
106
|
+
readonly path: string | null;
|
|
107
|
+
readonly nameReason: string | null;
|
|
108
|
+
readonly by: string | null;
|
|
109
|
+
readonly scope: string | null;
|
|
110
|
+
readonly scopes: readonly string[];
|
|
111
|
+
readonly exhausted: boolean;
|
|
112
|
+
readonly why: string | null;
|
|
113
|
+
readonly problems: readonly McpServerProblemView[];
|
|
114
|
+
readonly env: readonly string[];
|
|
115
|
+
readonly headers: readonly string[];
|
|
116
|
+
readonly args: readonly number[];
|
|
117
|
+
}
|
|
118
|
+
export interface McpServerEditPlanView {
|
|
119
|
+
readonly op: string;
|
|
120
|
+
readonly name: string;
|
|
121
|
+
readonly label: string;
|
|
122
|
+
readonly scope: string | null;
|
|
123
|
+
readonly renamedFrom: string | null;
|
|
124
|
+
readonly replaced: boolean;
|
|
125
|
+
readonly imported: readonly McpImportedView[];
|
|
126
|
+
readonly skipped: readonly McpSkippedView[];
|
|
127
|
+
}
|
|
128
|
+
export interface McpServerEditNoticeView {
|
|
129
|
+
readonly kind: string;
|
|
130
|
+
readonly scope: string | null;
|
|
131
|
+
readonly names: readonly string[];
|
|
132
|
+
readonly sentence: string;
|
|
133
|
+
}
|
|
134
|
+
export interface McpServerEditOutcomeView {
|
|
135
|
+
readonly receipt: LocalConfigReceipt | LocalConfigPreview;
|
|
136
|
+
readonly refusal: McpServerRefusalDetailView | null;
|
|
137
|
+
readonly plan: McpServerEditPlanView | null;
|
|
138
|
+
readonly notices: readonly McpServerEditNoticeView[];
|
|
139
|
+
readonly probe: 'ready' | 'removed' | 'not_active' | 'env_missing' | 'edit_refused' | null;
|
|
140
|
+
readonly sentence: string;
|
|
141
|
+
}
|
|
142
|
+
export type McpServerProbeView = {
|
|
143
|
+
readonly kind: 'face';
|
|
144
|
+
readonly probedAt: string;
|
|
145
|
+
readonly servers: readonly McpProbeServerRowView[];
|
|
146
|
+
} | {
|
|
147
|
+
readonly kind: 'refused';
|
|
148
|
+
readonly cause: string;
|
|
149
|
+
readonly sentence: string;
|
|
150
|
+
} | {
|
|
151
|
+
readonly kind: 'unknown';
|
|
152
|
+
readonly why: string;
|
|
153
|
+
readonly sentence: string;
|
|
154
|
+
} | {
|
|
155
|
+
readonly kind: 'not_probed';
|
|
156
|
+
readonly why: 'removed' | 'not_active' | 'env_missing' | 'edit_refused' | 'unavailable';
|
|
157
|
+
readonly sentence: string;
|
|
158
|
+
};
|
|
159
|
+
export declare function mcpServerReportView(report: McpServersReport, lang?: UiLanguage): McpServerReportView;
|
|
160
|
+
export declare function mcpServerEditOutcomeView(outcome: McpServerEditOutcome, lang?: UiLanguage): McpServerEditOutcomeView;
|
|
161
|
+
export declare function mcpServerEditSentence(outcome: McpServerEditOutcome, lang?: UiLanguage): string;
|
|
162
|
+
export declare function probeMcpServerEdit(edit: McpServerEdit, store: LocalConfigStorePort, ctx: McpServerEditContext, facade: McpProbeFacade, capability: McpProbeReading, lang?: UiLanguage): Promise<McpServerProbeView>;
|
|
163
|
+
export type McpViewCheck = true | {
|
|
164
|
+
readonly reason: string;
|
|
165
|
+
readonly field?: string;
|
|
166
|
+
};
|
|
167
|
+
export declare const checkMcpServerReportView: (v: unknown) => McpViewCheck;
|
|
168
|
+
export declare const checkMcpServerEditOutcomeView: (v: unknown) => McpViewCheck;
|
|
169
|
+
export declare const checkMcpServerProbeView: (v: unknown) => McpViewCheck;
|
|
170
|
+
export declare function checkMcpServerEditRequest(v: {} | null | undefined): McpViewCheck;
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { displayUntrusted, displayConfiguredUrl } from '../../displayUntrusted.js';
|
|
2
|
+
import { capForDisplay } from '../../fleetTaskDesc.js';
|
|
3
|
+
import { settingsCredentialKeyPaths } from '../../settingsCredentialKeyPaths.js';
|
|
4
|
+
import { probeMcpServers, MCP_PROBE_REFUSAL_CAUSES, MCP_PROBE_UNKNOWN_WHYS } from '../../mcpProbeWire.js';
|
|
5
|
+
import { msg, renderMessage } from '../../i18n/translate.js';
|
|
6
|
+
import { localConfigReceiptSentence } from '../kernel.js';
|
|
7
|
+
import { localConfigReceiptShapeOf } from '../receiptShape.js';
|
|
8
|
+
import { MCP_SERVER_SCOPES, MCP_SERVER_ROW_STATES, MCP_SERVER_WITHHELD_CAUSES, ownData, stringListOf } from './servers.js';
|
|
9
|
+
import { mcpRowBaseOf, mcpServerProbeTargetOf, MCP_SERVER_EDIT_OPS, refusalReasonOf, shapeMcpServerEdit } from './edit.js';
|
|
10
|
+
const LABEL_MAX = 140;
|
|
11
|
+
const TEXT_MAX = 400;
|
|
12
|
+
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
13
|
+
const own = (v, k) => {
|
|
14
|
+
const r = ownData(v, k);
|
|
15
|
+
return r.ok ? r.value : undefined;
|
|
16
|
+
};
|
|
17
|
+
const label = (s) => displayUntrusted(s, { max: LABEL_MAX });
|
|
18
|
+
const text = (s) => displayUntrusted(s, { max: TEXT_MAX });
|
|
19
|
+
const keysOf = (v) => {
|
|
20
|
+
try {
|
|
21
|
+
return isRecord(v) ? Object.keys(v) : [];
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const membersOf = (v) => {
|
|
28
|
+
const keys = keysOf(v);
|
|
29
|
+
return keys === undefined ? undefined : keys.map((k) => Object.freeze({ name: label(k), hasValue: typeof own(v, k) === 'string' && own(v, k) !== '' }));
|
|
30
|
+
};
|
|
31
|
+
function maskedArgsOf(list) {
|
|
32
|
+
try {
|
|
33
|
+
const masked = new Set();
|
|
34
|
+
for (const f of settingsCredentialKeyPaths({ args: [...list] })) {
|
|
35
|
+
const m = /^args\[(\d+)\]$/.exec(f.path);
|
|
36
|
+
if (m !== null)
|
|
37
|
+
masked.add(Number(m[1]));
|
|
38
|
+
}
|
|
39
|
+
return masked;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return 'all';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function argsView(v) {
|
|
46
|
+
const list = stringListOf(v) ?? [];
|
|
47
|
+
const masked = maskedArgsOf(list);
|
|
48
|
+
return list.map((a, i) => (masked === 'all' || masked.has(i) ? Object.freeze({ kind: 'masked' }) : Object.freeze({ kind: 'text', text: text(a) })));
|
|
49
|
+
}
|
|
50
|
+
function allowToolsView(entry) {
|
|
51
|
+
const r = ownData(entry, 'allowTools');
|
|
52
|
+
if (!r.ok)
|
|
53
|
+
return 'invalid';
|
|
54
|
+
if (r.value === undefined)
|
|
55
|
+
return null;
|
|
56
|
+
const list = stringListOf(r.value);
|
|
57
|
+
return list === undefined ? 'invalid' : Object.freeze(list.map(label));
|
|
58
|
+
}
|
|
59
|
+
const REMOTE_TYPES = new Set(['http', 'sse', 'ws', 'streamable-http', 'sse-ide', 'ws-ide', 'sdk', 'claudeai-proxy']);
|
|
60
|
+
function entryView(entry) {
|
|
61
|
+
try {
|
|
62
|
+
if (!isRecord(entry))
|
|
63
|
+
return Object.freeze({ transport: 'unreadable' });
|
|
64
|
+
const t = own(entry, 'type');
|
|
65
|
+
if (t !== undefined && t !== 'stdio' && t !== 'http')
|
|
66
|
+
return Object.freeze({ transport: 'unsupported', declaredType: typeof t === 'string' ? label(t) : 'unknown' });
|
|
67
|
+
if (t === 'http' || (t === undefined && typeof own(entry, 'url') === 'string' && own(entry, 'command') === undefined)) {
|
|
68
|
+
const url = own(entry, 'url');
|
|
69
|
+
const helper = own(entry, 'headersHelper');
|
|
70
|
+
const oauth = own(entry, 'oauth');
|
|
71
|
+
const cid = isRecord(oauth) ? own(oauth, 'clientId') : undefined;
|
|
72
|
+
const port = isRecord(oauth) ? own(oauth, 'callbackPort') : undefined;
|
|
73
|
+
const headers = membersOf(own(entry, 'headers'));
|
|
74
|
+
if (headers === undefined)
|
|
75
|
+
return Object.freeze({ transport: 'unreadable' });
|
|
76
|
+
return Object.freeze({
|
|
77
|
+
transport: 'http',
|
|
78
|
+
url: typeof url === 'string' ? capForDisplay(displayConfiguredUrl(url), TEXT_MAX) : '',
|
|
79
|
+
headers: Object.freeze(headers),
|
|
80
|
+
headersHelper: typeof helper === 'string' ? Object.freeze(argsView([helper])) : null,
|
|
81
|
+
oauth: isRecord(oauth) ? Object.freeze({ clientId: typeof cid === 'string' ? label(cid) : null, callbackPort: typeof port === 'number' && Number.isInteger(port) ? port : null }) : null,
|
|
82
|
+
allowTools: allowToolsView(entry),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (typeof t === 'string' && REMOTE_TYPES.has(t))
|
|
86
|
+
return Object.freeze({ transport: 'unsupported', declaredType: label(t) });
|
|
87
|
+
const cmd = own(entry, 'command');
|
|
88
|
+
const env = membersOf(own(entry, 'env'));
|
|
89
|
+
if (env === undefined)
|
|
90
|
+
return Object.freeze({ transport: 'unreadable' });
|
|
91
|
+
return Object.freeze({ transport: 'stdio', command: typeof cmd === 'string' ? text(cmd) : '', args: Object.freeze(argsView(own(entry, 'args'))), env: Object.freeze(env), allowTools: allowToolsView(entry) });
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return Object.freeze({ transport: 'unreadable' });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const problemView = (p) => Object.freeze({ reason: String(p.reason), path: typeof p.path === 'string' ? text(p.path) : null, sentence: text(String(p.notice)) });
|
|
98
|
+
const WITHHELD_KEYS = Object.freeze({
|
|
99
|
+
narrowing_conflict: 'config.mcpServers.withheld.narrowingConflict',
|
|
100
|
+
narrowing_invalid: 'config.mcpServers.withheld.narrowingInvalid',
|
|
101
|
+
layer_unreadable: 'config.mcpServers.withheld.layerUnreadable',
|
|
102
|
+
policy_locked: 'config.mcpServers.withheld.policyLocked',
|
|
103
|
+
});
|
|
104
|
+
function rowView(row, deployment, lang) {
|
|
105
|
+
const withheldBy = typeof row.withheldBy === 'string' ? row.withheldBy : null;
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
name: String(row.name),
|
|
108
|
+
label: label(String(row.name)),
|
|
109
|
+
scope: String(row.scope),
|
|
110
|
+
shadows: Object.freeze([...row.shadows].map(String)),
|
|
111
|
+
state: String(row.state),
|
|
112
|
+
withheldBy,
|
|
113
|
+
approvedBy: typeof row.approvedBy === 'string' ? row.approvedBy : null,
|
|
114
|
+
writable: row.writable === true,
|
|
115
|
+
base: row.writable === true ? mcpRowBaseOf(row.origin.revision) : null,
|
|
116
|
+
deploymentWins: deployment.has(row.name),
|
|
117
|
+
missing: Object.freeze([...row.missing].map(label)),
|
|
118
|
+
entry: entryView(row.entry),
|
|
119
|
+
problems: Object.freeze(row.state === 'unusable' ? (row.problems ?? []).map(problemView) : []),
|
|
120
|
+
sentence: withheldBy !== null && Object.hasOwn(WITHHELD_KEYS, withheldBy) ? renderMessage(msg(WITHHELD_KEYS[withheldBy]), lang) : null,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function rowViewOrUndefined(row, deployment, lang) {
|
|
124
|
+
try {
|
|
125
|
+
return rowView(row, deployment, lang);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function mcpServerReportView(report, lang) {
|
|
132
|
+
const deployment = report.deployment.state === 'read' ? new Set(report.deployment.names) : new Set();
|
|
133
|
+
const rows = [];
|
|
134
|
+
for (const r of report.rows) {
|
|
135
|
+
const v = rowViewOrUndefined(r, deployment, lang);
|
|
136
|
+
if (v !== undefined)
|
|
137
|
+
rows.push(v);
|
|
138
|
+
}
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
kind: 'mcp_servers',
|
|
141
|
+
rows: Object.freeze(rows),
|
|
142
|
+
unreadable: Object.freeze([...report.unreadable]),
|
|
143
|
+
locked: report.locked ?? null,
|
|
144
|
+
purpose: report.purpose,
|
|
145
|
+
approvalsWithheld: Object.freeze(report.approvalsWithheld.map((a) => Object.freeze({ layer: a.layer, cause: a.cause, sentence: renderMessage(msg('config.mcpServers.approvalsWithheld', { layer: a.layer }), lang) }))),
|
|
146
|
+
deployment: report.deployment.state === 'read' ? Object.freeze({ state: 'read', names: Object.freeze(report.deployment.names.map((n) => Object.freeze({ name: n, label: label(n) }))) }) : Object.freeze({ state: 'unknown', names: Object.freeze([]) }),
|
|
147
|
+
pluginRoster: report.pluginRoster,
|
|
148
|
+
promptsAndResources: 'not_yet',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const NOTICE_KEYS = Object.freeze({
|
|
152
|
+
shadowed_by: 'config.mcpServers.notice.shadowed',
|
|
153
|
+
endpoint_differs: 'config.mcpServers.notice.endpointDiffers',
|
|
154
|
+
deployment_wins: 'config.mcpServers.notice.deploymentWins',
|
|
155
|
+
env_missing: 'config.mcpServers.notice.envMissing',
|
|
156
|
+
});
|
|
157
|
+
export function mcpServerEditOutcomeView(outcome, lang) {
|
|
158
|
+
const r = outcome.receipt;
|
|
159
|
+
const refusal = outcome.refusal;
|
|
160
|
+
const d = refusal;
|
|
161
|
+
const strOrNull = (k) => (d !== undefined && typeof d[k] === 'string' ? d[k] : null);
|
|
162
|
+
const plan = outcome.plan;
|
|
163
|
+
const probe = outcome.probe === undefined ? null : outcome.probe.kind === 'spec' ? 'ready' : outcome.probe.why;
|
|
164
|
+
return Object.freeze({
|
|
165
|
+
receipt: r,
|
|
166
|
+
refusal: refusal === undefined
|
|
167
|
+
? null
|
|
168
|
+
: Object.freeze({
|
|
169
|
+
refusal: refusal.refusal,
|
|
170
|
+
path: refusal.refusal === 'edit_malformed' && refusal.path !== null ? text(refusal.path) : null,
|
|
171
|
+
nameReason: strOrNull('nameReason'),
|
|
172
|
+
by: strOrNull('by'),
|
|
173
|
+
scope: strOrNull('scope'),
|
|
174
|
+
scopes: Object.freeze(refusal.refusal === 'ambiguous_scope' ? [...refusal.scopes] : []),
|
|
175
|
+
exhausted: refusal.refusal === 'name_taken' && refusal.exhausted,
|
|
176
|
+
why: strOrNull('why'),
|
|
177
|
+
problems: Object.freeze(refusal.refusal === 'mcp_entry_invalid' ? refusal.problems.map(problemView) : []),
|
|
178
|
+
env: Object.freeze(refusal.refusal === 'credentials_need_reentry' ? refusal.env.map(label) : []),
|
|
179
|
+
headers: Object.freeze(refusal.refusal === 'credentials_need_reentry' ? refusal.headers.map(label) : []),
|
|
180
|
+
args: Object.freeze(refusal.refusal === 'credentials_need_reentry' ? [...refusal.args] : []),
|
|
181
|
+
}),
|
|
182
|
+
plan: plan === undefined
|
|
183
|
+
? null
|
|
184
|
+
: Object.freeze({
|
|
185
|
+
op: plan.op,
|
|
186
|
+
name: plan.name,
|
|
187
|
+
label: label(plan.name),
|
|
188
|
+
scope: plan.scope ?? null,
|
|
189
|
+
renamedFrom: plan.renamedFrom ?? null,
|
|
190
|
+
replaced: plan.replaced === true,
|
|
191
|
+
imported: Object.freeze((plan.imported ?? []).map((x) => Object.freeze({ name: x.name, label: label(x.name), renamedFrom: x.renamedFrom ?? null }))),
|
|
192
|
+
skipped: Object.freeze((plan.skipped ?? []).map((x) => Object.freeze({ name: x.name, label: label(x.name), reason: x.reason }))),
|
|
193
|
+
}),
|
|
194
|
+
notices: Object.freeze(outcome.notices.map((n) => {
|
|
195
|
+
const scope = n.kind === 'shadowed_by' || n.kind === 'endpoint_differs' ? n.scope : null;
|
|
196
|
+
const names = n.kind === 'env_missing' ? n.names.map(label) : [];
|
|
197
|
+
return Object.freeze({ kind: n.kind, scope, names: Object.freeze(names), sentence: renderMessage(msg(NOTICE_KEYS[n.kind], { scope: scope ?? '', names: names.join(', ') }), lang) });
|
|
198
|
+
})),
|
|
199
|
+
probe,
|
|
200
|
+
sentence: localConfigReceiptSentence(r, lang),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
export function mcpServerEditSentence(outcome, lang) {
|
|
204
|
+
try {
|
|
205
|
+
return localConfigReceiptSentence(outcome.receipt, lang);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return localConfigReceiptSentence({ kind: 'refused', reason: 'not_written' }, lang);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const authorizationCopy = (a) => Object.freeze({ reason: a.reason, ...(a.authorizeUrl === undefined ? {} : { authorizeUrl: a.authorizeUrl }), ...(a.resourceMetadataUrl === undefined ? {} : { resourceMetadataUrl: a.resourceMetadataUrl }) });
|
|
212
|
+
const PROBE_NONE_KEYS = Object.freeze({
|
|
213
|
+
removed: 'config.mcpServers.probe.removed',
|
|
214
|
+
not_active: 'config.mcpServers.probe.notActive',
|
|
215
|
+
env_missing: 'config.mcpServers.probe.envMissing',
|
|
216
|
+
edit_refused: 'config.mcpServers.probe.editRefusal',
|
|
217
|
+
});
|
|
218
|
+
export async function probeMcpServerEdit(edit, store, ctx, facade, capability, lang) {
|
|
219
|
+
try {
|
|
220
|
+
const target = await mcpServerProbeTargetOf(edit, store, ctx);
|
|
221
|
+
if (target.kind === 'unavailable')
|
|
222
|
+
return Object.freeze({ kind: 'not_probed', why: 'unavailable', sentence: localConfigReceiptSentence(target, lang) });
|
|
223
|
+
if (target.kind === 'refused')
|
|
224
|
+
return Object.freeze({ kind: 'not_probed', why: 'edit_refused', sentence: renderMessage(msg('config.mcpServers.probe.editRefusal'), lang) });
|
|
225
|
+
if (target.kind === 'none')
|
|
226
|
+
return Object.freeze({ kind: 'not_probed', why: target.why, sentence: renderMessage(msg(PROBE_NONE_KEYS[target.why]), lang) });
|
|
227
|
+
const spec = target.spec;
|
|
228
|
+
const out = await probeMcpServers(facade, { specs: [spec], capability });
|
|
229
|
+
if (out.status === 'face')
|
|
230
|
+
return Object.freeze({
|
|
231
|
+
kind: 'face',
|
|
232
|
+
probedAt: String(out.face.probedAt),
|
|
233
|
+
servers: Object.freeze(out.face.servers.map((s) => Object.freeze({
|
|
234
|
+
name: String(s.name),
|
|
235
|
+
label: label(String(s.name)),
|
|
236
|
+
status: String(s.status),
|
|
237
|
+
errorCode: typeof s.errorCode === 'string' ? s.errorCode : null,
|
|
238
|
+
toolCount: typeof s.toolCount === 'number' && Number.isFinite(s.toolCount) ? s.toolCount : null,
|
|
239
|
+
authorization: s.authorizationUnreadable === true ? 'unreadable' : s.authorization === undefined ? null : authorizationCopy(s.authorization),
|
|
240
|
+
}))),
|
|
241
|
+
});
|
|
242
|
+
if (out.status === 'refused')
|
|
243
|
+
return Object.freeze({ kind: 'refused', cause: out.cause, sentence: text(out.message) });
|
|
244
|
+
return Object.freeze({ kind: 'unknown', why: out.why, sentence: text(out.message) });
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return Object.freeze({ kind: 'not_probed', why: 'edit_refused', sentence: renderMessage(msg('config.mcpServers.probe.editRefusal'), lang) });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const fail = (reason, field) => (field === undefined ? { reason } : { reason, field });
|
|
251
|
+
const str = (v, at) => (typeof v === 'string' ? true : fail('must be a string', at));
|
|
252
|
+
const bool = (v, at) => (typeof v === 'boolean' ? true : fail('must be a boolean', at));
|
|
253
|
+
const nullable = (r) => (v, at) => v === null ? true : r(v, at);
|
|
254
|
+
const oneOf = (words) => (v, at) => typeof v === 'string' && words.includes(v) ? true : fail(`must be one of ${words.join('|')}`, at);
|
|
255
|
+
const int = (v, at) => (typeof v === 'number' && Number.isInteger(v) && v >= 0 ? true : fail('must be a non-negative integer', at));
|
|
256
|
+
const list = (r) => (v, at) => {
|
|
257
|
+
if (!Array.isArray(v))
|
|
258
|
+
return fail('must be an array', at);
|
|
259
|
+
for (let i = 0; i < v.length; i++) {
|
|
260
|
+
const d = Object.getOwnPropertyDescriptor(v, String(i));
|
|
261
|
+
if (d === undefined || !Object.hasOwn(d, 'value'))
|
|
262
|
+
return fail('sparse or accessor element', `${at}[${i}]`);
|
|
263
|
+
const x = r(d.value, `${at}[${i}]`);
|
|
264
|
+
if (x !== true)
|
|
265
|
+
return x;
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
};
|
|
269
|
+
const shape = (spec) => (v, at) => {
|
|
270
|
+
if (!isRecord(v))
|
|
271
|
+
return fail('must be a plain object', at);
|
|
272
|
+
const keys = Object.keys(v);
|
|
273
|
+
for (const k of keys)
|
|
274
|
+
if (!Object.hasOwn(spec, k))
|
|
275
|
+
return fail(`unknown key ${k}`, at === '' ? k : `${at}.${k}`);
|
|
276
|
+
for (const k of Object.keys(spec)) {
|
|
277
|
+
const d = Object.getOwnPropertyDescriptor(v, k);
|
|
278
|
+
const p = at === '' ? k : `${at}.${k}`;
|
|
279
|
+
if (d === undefined)
|
|
280
|
+
return fail('missing key', p);
|
|
281
|
+
if (!Object.hasOwn(d, 'value'))
|
|
282
|
+
return fail('accessor', p);
|
|
283
|
+
const x = spec[k](d.value, p);
|
|
284
|
+
if (x !== true)
|
|
285
|
+
return x;
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
};
|
|
289
|
+
const arms = (key, table) => (v, at) => {
|
|
290
|
+
const w = own(v, key);
|
|
291
|
+
if (typeof w !== 'string' || !Object.hasOwn(table, w))
|
|
292
|
+
return fail(`${key} must be one of ${Object.keys(table).join('|')}`, at === '' ? key : `${at}.${key}`);
|
|
293
|
+
return table[w](v, at);
|
|
294
|
+
};
|
|
295
|
+
const ARG = arms('kind', { text: shape({ kind: str, text: str }), masked: shape({ kind: str }) });
|
|
296
|
+
const MEMBER = shape({ name: str, hasValue: bool });
|
|
297
|
+
const AUTHORIZATION = (v, at) => {
|
|
298
|
+
if (!isRecord(v))
|
|
299
|
+
return fail('must be a plain object', at);
|
|
300
|
+
for (const k of Object.keys(v))
|
|
301
|
+
if (k !== 'reason' && k !== 'authorizeUrl' && k !== 'resourceMetadataUrl')
|
|
302
|
+
return fail(`unknown key ${k}`, `${at}.${k}`);
|
|
303
|
+
for (const k of ['reason', 'authorizeUrl', 'resourceMetadataUrl']) {
|
|
304
|
+
const d = Object.getOwnPropertyDescriptor(v, k);
|
|
305
|
+
if (d === undefined) {
|
|
306
|
+
if (k === 'reason')
|
|
307
|
+
return fail('missing key', `${at}.${k}`);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (!Object.hasOwn(d, 'value'))
|
|
311
|
+
return fail('accessor', `${at}.${k}`);
|
|
312
|
+
if (typeof d.value !== 'string' || d.value === '')
|
|
313
|
+
return fail('must be a non-empty string', `${at}.${k}`);
|
|
314
|
+
}
|
|
315
|
+
return true;
|
|
316
|
+
};
|
|
317
|
+
const ALLOW = (v, at) => (v === null || v === 'invalid' ? true : list(str)(v, at));
|
|
318
|
+
const ENTRY = arms('transport', {
|
|
319
|
+
stdio: shape({ transport: str, command: str, args: list(ARG), env: list(MEMBER), allowTools: ALLOW }),
|
|
320
|
+
http: shape({ transport: str, url: str, headers: list(MEMBER), headersHelper: nullable(list(ARG)), oauth: nullable(shape({ clientId: nullable(str), callbackPort: nullable(int) })), allowTools: ALLOW }),
|
|
321
|
+
unsupported: shape({ transport: str, declaredType: str }),
|
|
322
|
+
unreadable: shape({ transport: str }),
|
|
323
|
+
});
|
|
324
|
+
const PROBLEM = shape({ reason: str, path: nullable(str), sentence: str });
|
|
325
|
+
const SCOPE_WORDS = Object.freeze([...MCP_SERVER_SCOPES, 'managed', 'enterprise', 'claudeai', 'plugin', 'agent', 'dynamic']);
|
|
326
|
+
const ROW = shape({
|
|
327
|
+
name: str,
|
|
328
|
+
label: str,
|
|
329
|
+
scope: oneOf(SCOPE_WORDS),
|
|
330
|
+
shadows: list(oneOf(SCOPE_WORDS)),
|
|
331
|
+
state: oneOf(MCP_SERVER_ROW_STATES),
|
|
332
|
+
withheldBy: nullable(oneOf(MCP_SERVER_WITHHELD_CAUSES)),
|
|
333
|
+
approvedBy: nullable(oneOf(['settings', 'session', 'bypass', 'non_interactive'])),
|
|
334
|
+
writable: bool,
|
|
335
|
+
base: nullable(str),
|
|
336
|
+
deploymentWins: bool,
|
|
337
|
+
missing: list(str),
|
|
338
|
+
entry: ENTRY,
|
|
339
|
+
problems: list(PROBLEM),
|
|
340
|
+
sentence: nullable(str),
|
|
341
|
+
});
|
|
342
|
+
const REPORT = shape({
|
|
343
|
+
kind: oneOf(['mcp_servers']),
|
|
344
|
+
rows: list(ROW),
|
|
345
|
+
unreadable: list(oneOf(['global', 'project', 'enterprise', 'managed', 'supplementary'])),
|
|
346
|
+
locked: nullable(oneOf(['enterprise_config', 'plugin_only'])),
|
|
347
|
+
purpose: oneOf(['report', 'load']),
|
|
348
|
+
approvalsWithheld: list(shape({ layer: oneOf(['projectSettings', 'localSettings']), cause: oneOf(['trust_withheld']), sentence: str })),
|
|
349
|
+
deployment: arms('state', { unknown: shape({ state: str, names: (v, at) => (Array.isArray(v) && v.length === 0 ? true : fail('must be empty', at)) }), read: shape({ state: str, names: list(shape({ name: str, label: str })) }) }),
|
|
350
|
+
pluginRoster: oneOf(['host_supplied', 'absent']),
|
|
351
|
+
promptsAndResources: oneOf(['not_yet']),
|
|
352
|
+
});
|
|
353
|
+
const RECEIPT = (v, at) => localConfigReceiptShapeOf(v, at);
|
|
354
|
+
const OUTCOME_SHAPE = shape({
|
|
355
|
+
receipt: RECEIPT,
|
|
356
|
+
refusal: nullable(shape({ refusal: str, path: nullable(str), nameReason: nullable(str), by: nullable(str), scope: nullable(str), scopes: list(oneOf(MCP_SERVER_SCOPES)), exhausted: bool, why: nullable(str), problems: list(PROBLEM), env: list(str), headers: list(str), args: list(int) })),
|
|
357
|
+
plan: nullable(shape({
|
|
358
|
+
op: oneOf(MCP_SERVER_EDIT_OPS),
|
|
359
|
+
name: str,
|
|
360
|
+
label: str,
|
|
361
|
+
scope: nullable(oneOf(MCP_SERVER_SCOPES)),
|
|
362
|
+
renamedFrom: nullable(str),
|
|
363
|
+
replaced: bool,
|
|
364
|
+
imported: list(shape({ name: str, label: str, renamedFrom: nullable(str) })),
|
|
365
|
+
skipped: list(shape({ name: str, label: str, reason: str })),
|
|
366
|
+
})),
|
|
367
|
+
notices: list(shape({ kind: oneOf(['shadowed_by', 'endpoint_differs', 'deployment_wins', 'env_missing']), scope: nullable(str), names: list(str), sentence: str })),
|
|
368
|
+
probe: nullable(oneOf(['ready', 'removed', 'not_active', 'env_missing', 'edit_refused'])),
|
|
369
|
+
sentence: str,
|
|
370
|
+
});
|
|
371
|
+
const OUTCOME = (v, at) => {
|
|
372
|
+
const verdict = OUTCOME_SHAPE(v, at);
|
|
373
|
+
if (verdict !== true)
|
|
374
|
+
return verdict;
|
|
375
|
+
const refusal = own(v, 'refusal');
|
|
376
|
+
if (refusal === null)
|
|
377
|
+
return true;
|
|
378
|
+
const receipt = own(v, 'receipt');
|
|
379
|
+
const word = own(refusal, 'refusal');
|
|
380
|
+
return own(receipt, 'kind') === 'refused' && typeof word === 'string' && refusalReasonOf(word) === own(receipt, 'reason') ? true : fail('refusal mismatch', at === '' ? 'refusal' : `${at}.refusal`);
|
|
381
|
+
};
|
|
382
|
+
const PROBE = arms('kind', {
|
|
383
|
+
face: shape({
|
|
384
|
+
kind: str,
|
|
385
|
+
probedAt: str,
|
|
386
|
+
servers: list(shape({
|
|
387
|
+
name: str,
|
|
388
|
+
label: str,
|
|
389
|
+
status: str,
|
|
390
|
+
errorCode: nullable(str),
|
|
391
|
+
toolCount: nullable((v, at) => (typeof v === 'number' && Number.isFinite(v) ? true : fail('must be a finite number', at))),
|
|
392
|
+
authorization: (v, at) => (v === null || v === 'unreadable' ? true : AUTHORIZATION(v, at)),
|
|
393
|
+
})),
|
|
394
|
+
}),
|
|
395
|
+
refused: shape({ kind: str, cause: oneOf(MCP_PROBE_REFUSAL_CAUSES), sentence: str }),
|
|
396
|
+
unknown: shape({ kind: str, why: oneOf(MCP_PROBE_UNKNOWN_WHYS), sentence: str }),
|
|
397
|
+
not_probed: shape({ kind: str, why: oneOf(['removed', 'not_active', 'env_missing', 'edit_refused', 'unavailable']), sentence: str }),
|
|
398
|
+
});
|
|
399
|
+
const guarded = (r) => (v) => {
|
|
400
|
+
try {
|
|
401
|
+
return r(v, '');
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
return fail('payload could not be read');
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
export const checkMcpServerReportView = guarded(REPORT);
|
|
408
|
+
export const checkMcpServerEditOutcomeView = guarded(OUTCOME);
|
|
409
|
+
export const checkMcpServerProbeView = guarded(PROBE);
|
|
410
|
+
export function checkMcpServerEditRequest(v) {
|
|
411
|
+
try {
|
|
412
|
+
if (!isRecord(v))
|
|
413
|
+
return fail('request must be a plain object');
|
|
414
|
+
for (const k of Object.keys(v))
|
|
415
|
+
if (k !== 'sessionId' && k !== 'edit' && k !== 'dryRun')
|
|
416
|
+
return fail(`unknown key ${k}`, k);
|
|
417
|
+
const sid = own(v, 'sessionId');
|
|
418
|
+
if (sid !== undefined && (typeof sid !== 'string' || sid === ''))
|
|
419
|
+
return fail('sessionId must be a non-empty string', 'sessionId');
|
|
420
|
+
const dry = own(v, 'dryRun');
|
|
421
|
+
if (dry !== undefined && dry !== true)
|
|
422
|
+
return fail('dryRun must be true when present', 'dryRun');
|
|
423
|
+
const s = shapeMcpServerEdit(own(v, 'edit'));
|
|
424
|
+
return s.ok ? true : fail('edit is not a valid MCP server edit', s.path === null ? 'edit' : `edit.${s.path}`);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return fail('request could not be read');
|
|
428
|
+
}
|
|
429
|
+
}
|