salmon-loop 0.2.16 → 0.3.1
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/dist/cli/authorization/non-interactive.js +7 -21
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/parallel.js +46 -41
- package/dist/cli/commands/run/assistant-message.js +3 -0
- package/dist/cli/commands/run/handler.js +2 -1
- package/dist/cli/commands/serve.js +112 -156
- package/dist/cli/headless/json-protocol.js +1 -1
- package/dist/cli/headless/stream-json-protocol.js +3 -2
- package/dist/cli/program-bootstrap.js +2 -2
- package/dist/cli/slash/runtime.js +5 -1
- package/dist/core/adapters/fs/node-fs.js +1 -0
- package/dist/core/backends/salmon-loop/task-executor.js +1 -0
- package/dist/core/benchmark/patch-artifact.js +1 -1
- package/dist/core/context/service.js +5 -2
- package/dist/core/extensions/index.js +2 -35
- package/dist/core/extensions/merge.js +14 -0
- package/dist/core/extensions/redact.js +9 -3
- package/dist/core/extensions/schemas.js +2 -51
- package/dist/core/facades/cli-authorization-non-interactive.js +1 -1
- package/dist/core/facades/cli-program-bootstrap.js +1 -0
- package/dist/core/facades/cli-serve.js +2 -1
- package/dist/core/grizzco/dsl/strategies.js +1 -3
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +12 -7
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +23 -23
- package/dist/core/grizzco/engine/transaction/report-mapper.js +3 -0
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +14 -0
- package/dist/core/grizzco/flows/AutopilotFlow.js +1 -0
- package/dist/core/grizzco/flows/SalmonLoopFlow.js +1 -0
- package/dist/core/grizzco/steps/apply.js +0 -7
- package/dist/core/grizzco/steps/autopilot.js +108 -6
- package/dist/core/grizzco/steps/preflight.js +10 -0
- package/dist/core/grizzco/steps/tool-runtime.js +1 -0
- package/dist/core/interaction/events/bus.js +14 -0
- package/dist/core/interaction/orchestration/facade.js +11 -1
- package/dist/core/llm/ai-sdk/request-params.js +40 -1
- package/dist/core/mcp/bridge/index.js +4 -0
- package/dist/core/mcp/bridge/prompt-command-provider.js +261 -0
- package/dist/core/mcp/bridge/resource-context-provider.js +259 -0
- package/dist/core/mcp/bridge/tool-bridge.js +303 -0
- package/dist/core/mcp/cache/resource-cache.js +41 -0
- package/dist/core/mcp/catalog/discovery.js +51 -0
- package/dist/core/mcp/catalog/notification-router.js +28 -0
- package/dist/core/mcp/catalog/prompt-catalog.js +4 -0
- package/dist/core/mcp/catalog/resource-catalog.js +7 -0
- package/dist/core/mcp/catalog/tool-catalog.js +4 -0
- package/dist/core/mcp/client/connection-manager.js +239 -0
- package/dist/core/mcp/client/lifecycle.js +13 -0
- package/dist/core/mcp/client/transport-factory.js +168 -0
- package/dist/core/mcp/config/index.js +32 -0
- package/dist/core/mcp/config/schema-v2.js +129 -0
- package/dist/core/mcp/host/elicitation-provider.js +209 -0
- package/dist/core/mcp/host/roots-provider.js +70 -0
- package/dist/core/mcp/host/sampling-provider.js +170 -0
- package/dist/core/mcp/index.js +4 -0
- package/dist/core/mcp/observability/events.js +19 -0
- package/dist/core/mcp/policy/approval-policy.js +2 -0
- package/dist/core/mcp/policy/classifier.js +172 -0
- package/dist/core/mcp/policy/grants.js +356 -0
- package/dist/core/mcp/policy/uri-policy.js +60 -0
- package/dist/core/mcp/schema/json-schema-to-zod.js +511 -0
- package/dist/core/mcp/types.js +2 -0
- package/dist/core/protocols/a2a/agent-card.js +38 -12
- package/dist/core/protocols/a2a/sdk/executor.js +105 -36
- package/dist/core/protocols/a2a/sdk/server.js +1311 -3
- package/dist/core/protocols/acp/acp-checkpoint-probe.js +113 -0
- package/dist/core/protocols/acp/acp-session-persistence.js +336 -0
- package/dist/core/protocols/acp/acp-types.js +17 -0
- package/dist/core/protocols/acp/formal-agent.js +389 -502
- package/dist/core/protocols/acp/handlers.js +3 -0
- package/dist/core/protocols/acp/permission-provider.js +11 -39
- package/dist/core/protocols/acp/stdio-server.js +20 -1
- package/dist/core/protocols/acp/tool-kind-mapping.js +62 -0
- package/dist/core/protocols/shared/flow-mode-mapping.js +0 -8
- package/dist/core/public-capabilities/flow-mode-metadata.js +0 -6
- package/dist/core/public-capabilities/projections.js +1 -0
- package/dist/core/runtime/agent-server-runtime.js +2 -3
- package/dist/core/runtime/spawn-command.js +8 -2
- package/dist/core/runtime/spawn-interactive.js +26 -0
- package/dist/core/session/manager.js +48 -25
- package/dist/core/tools/builtin/index.js +6 -1
- package/dist/core/tools/builtin/proposal.js +0 -7
- package/dist/core/tools/builtin/workspace.js +76 -0
- package/dist/core/tools/dispatcher.js +1 -0
- package/dist/core/tools/loader.js +92 -46
- package/dist/core/verification/runner.js +60 -31
- package/dist/core/version.js +17 -0
- package/dist/core/workspace/capabilities.js +80 -0
- package/dist/locales/en.js +17 -3
- package/package.json +4 -2
- package/dist/core/protocols/a2a/mapper.js +0 -14
- package/dist/core/protocols/a2a/sdk/auth-middleware.js +0 -31
- package/dist/core/protocols/a2a/task-projection.js +0 -45
- package/dist/core/protocols/acp/checkpoint-meta.js +0 -2
- package/dist/core/tools/mcp/client.js +0 -308
- package/dist/core/tools/mcp/loader.js +0 -110
- package/dist/core/tools/mcp/schema.js +0 -54
- package/dist/core/tools/mcp/streamable-http.js +0 -101
- package/dist/core/tools/mcp/types.js +0 -26
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { classifyMcpTool } from './classifier.js';
|
|
2
|
+
import { evaluateMcpUriPolicy, matchesMcpPattern, McpUriPolicy, } from './uri-policy.js';
|
|
3
|
+
export function decideMcpApprovalPolicy(input) {
|
|
4
|
+
const risk = input.remote ? raiseRisk(input.risk) : input.risk;
|
|
5
|
+
if (input.capability === 'sampling') {
|
|
6
|
+
return {
|
|
7
|
+
outcome: input.defaultOutcome ?? 'deny',
|
|
8
|
+
reason: input.defaultOutcome
|
|
9
|
+
? 'MCP_SAMPLING_EXPLICIT_GRANT'
|
|
10
|
+
: 'MCP_SAMPLING_DENIED_BY_DEFAULT',
|
|
11
|
+
risk,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (input.capability === 'elicitation') {
|
|
15
|
+
return {
|
|
16
|
+
outcome: input.defaultOutcome ?? 'ask',
|
|
17
|
+
reason: 'MCP_ELICITATION_APPROVAL_REQUIRED_BY_DEFAULT',
|
|
18
|
+
risk,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (risk === 'high') {
|
|
22
|
+
return {
|
|
23
|
+
outcome: input.defaultOutcome ?? 'ask',
|
|
24
|
+
reason: 'MCP_HIGH_RISK_APPROVAL_REQUIRED',
|
|
25
|
+
risk,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
outcome: input.defaultOutcome ?? 'allow',
|
|
30
|
+
reason: 'MCP_GRANTED',
|
|
31
|
+
risk,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export class McpPolicyEngine {
|
|
35
|
+
uriPolicy = new McpUriPolicy();
|
|
36
|
+
grants;
|
|
37
|
+
constructor(input = []) {
|
|
38
|
+
this.grants = Array.isArray(input) ? input : (input.grants ?? []);
|
|
39
|
+
}
|
|
40
|
+
decide(request) {
|
|
41
|
+
if (request.capability === 'tools') {
|
|
42
|
+
return this.decideTool({
|
|
43
|
+
server: request.server.name,
|
|
44
|
+
toolName: request.target ?? request.tool?.name ?? '',
|
|
45
|
+
phase: request.phase,
|
|
46
|
+
classification: request.tool
|
|
47
|
+
? classifyMcpTool(request.tool, { trust: request.server.trust ?? 'local' })
|
|
48
|
+
: undefined,
|
|
49
|
+
remote: request.server.trust === 'remote' || request.server.transport === 'http',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (request.capability === 'resources') {
|
|
53
|
+
return this.decideResource({
|
|
54
|
+
server: request.server.name,
|
|
55
|
+
uri: request.target ?? '',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (request.capability === 'prompts') {
|
|
59
|
+
return this.decidePrompt({
|
|
60
|
+
server: request.server.name,
|
|
61
|
+
name: request.target ?? '',
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (request.capability === 'roots') {
|
|
65
|
+
return this.decideRoots(request.server.name, request.target);
|
|
66
|
+
}
|
|
67
|
+
if (request.capability === 'sampling') {
|
|
68
|
+
return this.decideSampling(request.server.name, request.target);
|
|
69
|
+
}
|
|
70
|
+
return this.decideElicitation(request.server.name, request.target);
|
|
71
|
+
}
|
|
72
|
+
decideTool(input) {
|
|
73
|
+
const grant = this.grants.find((item) => 'kind' in item &&
|
|
74
|
+
item.kind === 'tool' &&
|
|
75
|
+
matchesServer(item.server, input.server) &&
|
|
76
|
+
matchesMcpPattern(input.toolName, item.namePattern));
|
|
77
|
+
const genericGrant = this.findGenericGrant('tools', input.server, 'call', input.toolName, input.phase);
|
|
78
|
+
const matchedGrant = grant ?? genericGrant;
|
|
79
|
+
if (!matchedGrant) {
|
|
80
|
+
return deny('MCP_TOOL_NOT_GRANTED', {
|
|
81
|
+
server: input.server,
|
|
82
|
+
capability: 'tools',
|
|
83
|
+
action: 'call',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const phaseAllowed = grant
|
|
87
|
+
? input.phase !== undefined && grant.phases.includes(input.phase)
|
|
88
|
+
: matchesPhase(genericGrant?.phase, input.phase);
|
|
89
|
+
if (!phaseAllowed) {
|
|
90
|
+
return deny('MCP_TOOL_PHASE_DENIED', {
|
|
91
|
+
grant: matchedGrant,
|
|
92
|
+
server: input.server,
|
|
93
|
+
capability: 'tools',
|
|
94
|
+
action: 'call',
|
|
95
|
+
phase: input.phase,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const hasWriteRisk = input.classification?.riskLevel === 'high' ||
|
|
99
|
+
input.classification?.sideEffects.some((effect) => ['fs_write', 'git_write', 'process', 'network'].includes(effect));
|
|
100
|
+
const risk = input.remote
|
|
101
|
+
? raiseRisk(input.classification?.riskLevel ?? 'medium')
|
|
102
|
+
: (input.classification?.riskLevel ?? 'medium');
|
|
103
|
+
const outcome = grant
|
|
104
|
+
? legacyToolOutcome(grant.approval, Boolean(hasWriteRisk))
|
|
105
|
+
: (genericGrant?.outcome ??
|
|
106
|
+
decideMcpApprovalPolicy({ capability: 'tools', risk, remote: input.remote }).outcome);
|
|
107
|
+
return allowOrAsk(outcome, matchedGrant.reason ?? 'MCP_TOOL_GRANTED', {
|
|
108
|
+
grant: matchedGrant,
|
|
109
|
+
risk,
|
|
110
|
+
server: input.server,
|
|
111
|
+
capability: 'tools',
|
|
112
|
+
action: 'call',
|
|
113
|
+
phase: input.phase,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
decideResource(input) {
|
|
117
|
+
const grant = this.grants.find((item) => 'kind' in item &&
|
|
118
|
+
item.kind === 'resource' &&
|
|
119
|
+
matchesServer(item.server, input.server) &&
|
|
120
|
+
this.uriPolicy.isAllowed(input.uri, [item.uriPattern]));
|
|
121
|
+
const genericGrant = grant ?? this.findGenericGrant('resources', input.server, 'read', input.uri);
|
|
122
|
+
if (!genericGrant) {
|
|
123
|
+
return deny('MCP_RESOURCE_URI_DENIED', {
|
|
124
|
+
server: input.server,
|
|
125
|
+
capability: 'resources',
|
|
126
|
+
action: 'read',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (!('kind' in genericGrant)) {
|
|
130
|
+
const rules = Array.isArray(genericGrant.target)
|
|
131
|
+
? genericGrant.target
|
|
132
|
+
: genericGrant.target
|
|
133
|
+
? [genericGrant.target]
|
|
134
|
+
: [];
|
|
135
|
+
const uriDecision = evaluateMcpUriPolicy(input.uri, rules);
|
|
136
|
+
if (!uriDecision.allowed) {
|
|
137
|
+
return deny(uriDecision.reason, {
|
|
138
|
+
grant: genericGrant,
|
|
139
|
+
server: input.server,
|
|
140
|
+
capability: 'resources',
|
|
141
|
+
action: 'read',
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return allowOrAsk('allow', genericGrant.reason ?? 'MCP_RESOURCE_GRANTED', {
|
|
146
|
+
grant: genericGrant,
|
|
147
|
+
risk: 'low',
|
|
148
|
+
server: input.server,
|
|
149
|
+
capability: 'resources',
|
|
150
|
+
action: 'read',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
decidePrompt(input) {
|
|
154
|
+
const grant = this.grants.find((item) => 'kind' in item &&
|
|
155
|
+
item.kind === 'prompt' &&
|
|
156
|
+
matchesServer(item.server, input.server) &&
|
|
157
|
+
matchesMcpPattern(input.name, item.namePattern) &&
|
|
158
|
+
item.exposeAs !== 'none');
|
|
159
|
+
const genericGrant = grant ?? this.findGenericGrant('prompts', input.server, 'read', input.name);
|
|
160
|
+
return genericGrant
|
|
161
|
+
? allowOrAsk('allow', genericGrant.reason ?? 'MCP_PROMPT_GRANTED', {
|
|
162
|
+
grant: genericGrant,
|
|
163
|
+
risk: 'low',
|
|
164
|
+
server: input.server,
|
|
165
|
+
capability: 'prompts',
|
|
166
|
+
action: 'read',
|
|
167
|
+
})
|
|
168
|
+
: deny('MCP_PROMPT_DENIED', {
|
|
169
|
+
server: input.server,
|
|
170
|
+
capability: 'prompts',
|
|
171
|
+
action: 'read',
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
decideRoots(server, target) {
|
|
175
|
+
const grant = this.grants.find((item) => 'kind' in item &&
|
|
176
|
+
item.kind === 'roots' &&
|
|
177
|
+
matchesServer(item.server, server) &&
|
|
178
|
+
item.mode !== 'none');
|
|
179
|
+
const genericGrant = grant ?? this.findGenericGrant('roots', server, 'read', target);
|
|
180
|
+
return genericGrant
|
|
181
|
+
? allowOrAsk('allow', genericGrant.reason ?? 'MCP_ROOTS_GRANTED', {
|
|
182
|
+
grant: genericGrant,
|
|
183
|
+
risk: 'low',
|
|
184
|
+
server,
|
|
185
|
+
capability: 'roots',
|
|
186
|
+
action: 'read',
|
|
187
|
+
})
|
|
188
|
+
: deny('MCP_ROOTS_DENIED', { server, capability: 'roots', action: 'read' });
|
|
189
|
+
}
|
|
190
|
+
decideSampling(server, target) {
|
|
191
|
+
const grant = this.grants.find((item) => 'kind' in item &&
|
|
192
|
+
item.kind === 'sampling' &&
|
|
193
|
+
matchesServer(item.server, server) &&
|
|
194
|
+
item.enabled);
|
|
195
|
+
const genericGrant = grant ?? this.findGenericGrant('sampling', server, 'request', target);
|
|
196
|
+
if (!genericGrant) {
|
|
197
|
+
return deny('MCP_SAMPLING_DENIED', { server, capability: 'sampling', action: 'request' });
|
|
198
|
+
}
|
|
199
|
+
const outcome = 'kind' in genericGrant ? 'ask' : (genericGrant.outcome ?? 'deny');
|
|
200
|
+
return allowOrAsk(outcome, genericGrant.reason ?? 'MCP_SAMPLING_GRANTED', {
|
|
201
|
+
grant: genericGrant,
|
|
202
|
+
risk: 'high',
|
|
203
|
+
server,
|
|
204
|
+
capability: 'sampling',
|
|
205
|
+
action: 'request',
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
decideElicitation(server, target) {
|
|
209
|
+
const grant = this.grants.find((item) => 'kind' in item &&
|
|
210
|
+
item.kind === 'elicitation' &&
|
|
211
|
+
matchesServer(item.server, server) &&
|
|
212
|
+
item.enabled);
|
|
213
|
+
const genericGrant = grant ?? this.findGenericGrant('elicitation', server, 'request', target);
|
|
214
|
+
if (!genericGrant) {
|
|
215
|
+
return allowOrAsk('ask', 'MCP_ELICITATION_APPROVAL_REQUIRED_BY_DEFAULT', {
|
|
216
|
+
risk: 'medium',
|
|
217
|
+
server,
|
|
218
|
+
capability: 'elicitation',
|
|
219
|
+
action: 'request',
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const outcome = 'kind' in genericGrant ? 'ask' : (genericGrant.outcome ?? 'ask');
|
|
223
|
+
return allowOrAsk(outcome, genericGrant.reason ?? 'MCP_ELICITATION_GRANTED', {
|
|
224
|
+
grant: genericGrant,
|
|
225
|
+
risk: 'medium',
|
|
226
|
+
server,
|
|
227
|
+
capability: 'elicitation',
|
|
228
|
+
action: 'request',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
findGenericGrant(capability, server, action, target, phase) {
|
|
232
|
+
return this.grants.find((grant) => !('kind' in grant) &&
|
|
233
|
+
grant.capability === capability &&
|
|
234
|
+
matchesServer(grant.server, server) &&
|
|
235
|
+
matchesAction(grant.actions, action) &&
|
|
236
|
+
matchesGenericTarget(grant.target, target) &&
|
|
237
|
+
matchesPhase(grant.phase, phase));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
export function buildMcpGrantsFromCapabilities(server, capabilities) {
|
|
241
|
+
const grants = [];
|
|
242
|
+
for (const namePattern of capabilities.tools.allow) {
|
|
243
|
+
grants.push({
|
|
244
|
+
kind: 'tool',
|
|
245
|
+
server,
|
|
246
|
+
namePattern,
|
|
247
|
+
phases: capabilities.tools.phases,
|
|
248
|
+
approval: capabilities.tools.approval,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
for (const uriPattern of capabilities.resources.allowUris) {
|
|
252
|
+
grants.push({
|
|
253
|
+
kind: 'resource',
|
|
254
|
+
server,
|
|
255
|
+
uriPattern,
|
|
256
|
+
autoInclude: capabilities.resources.autoInclude,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
for (const namePattern of capabilities.prompts.allow) {
|
|
260
|
+
grants.push({
|
|
261
|
+
kind: 'prompt',
|
|
262
|
+
server,
|
|
263
|
+
namePattern,
|
|
264
|
+
exposeAs: capabilities.prompts.exposeAs,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
grants.push({ kind: 'roots', server, mode: capabilities.roots.mode });
|
|
268
|
+
grants.push({
|
|
269
|
+
kind: 'sampling',
|
|
270
|
+
server,
|
|
271
|
+
enabled: capabilities.sampling.enabled,
|
|
272
|
+
maxTokens: capabilities.sampling.maxTokens,
|
|
273
|
+
maxDepth: capabilities.sampling.maxDepth,
|
|
274
|
+
});
|
|
275
|
+
grants.push({ kind: 'elicitation', server, enabled: capabilities.elicitation.enabled });
|
|
276
|
+
return grants;
|
|
277
|
+
}
|
|
278
|
+
export function getCapabilityKindForGrant(grant) {
|
|
279
|
+
if ('capability' in grant)
|
|
280
|
+
return grant.capability;
|
|
281
|
+
if (grant.kind === 'tool')
|
|
282
|
+
return 'tools';
|
|
283
|
+
if (grant.kind === 'resource')
|
|
284
|
+
return 'resources';
|
|
285
|
+
if (grant.kind === 'prompt')
|
|
286
|
+
return 'prompts';
|
|
287
|
+
return grant.kind;
|
|
288
|
+
}
|
|
289
|
+
export function getGrantServer(grant) {
|
|
290
|
+
return grant.server;
|
|
291
|
+
}
|
|
292
|
+
export function grantOutcomeToApprovalMode(outcome) {
|
|
293
|
+
if (outcome === 'ask')
|
|
294
|
+
return 'ask';
|
|
295
|
+
return 'never';
|
|
296
|
+
}
|
|
297
|
+
function legacyToolOutcome(approval, hasWriteRisk) {
|
|
298
|
+
if (approval === 'ask')
|
|
299
|
+
return 'ask';
|
|
300
|
+
if (approval === 'write_requires_confirmation' && hasWriteRisk)
|
|
301
|
+
return 'ask';
|
|
302
|
+
return 'allow';
|
|
303
|
+
}
|
|
304
|
+
function allowOrAsk(outcome, reason, details) {
|
|
305
|
+
return {
|
|
306
|
+
...details,
|
|
307
|
+
allowed: outcome === 'allow',
|
|
308
|
+
approvalRequired: outcome === 'ask',
|
|
309
|
+
needsApproval: outcome === 'ask',
|
|
310
|
+
outcome,
|
|
311
|
+
reason,
|
|
312
|
+
denyReason: outcome === 'deny' ? reason : undefined,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function deny(reason, details) {
|
|
316
|
+
return {
|
|
317
|
+
...details,
|
|
318
|
+
allowed: false,
|
|
319
|
+
approvalRequired: false,
|
|
320
|
+
needsApproval: false,
|
|
321
|
+
outcome: 'deny',
|
|
322
|
+
reason,
|
|
323
|
+
denyReason: reason,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function matchesServer(pattern, server) {
|
|
327
|
+
return pattern === '*' || pattern === server;
|
|
328
|
+
}
|
|
329
|
+
function matchesAction(actions, action) {
|
|
330
|
+
return !actions?.length || actions.includes('*') || actions.includes(action);
|
|
331
|
+
}
|
|
332
|
+
function matchesPhase(grantPhase, requestPhase) {
|
|
333
|
+
if (!grantPhase)
|
|
334
|
+
return true;
|
|
335
|
+
if (!requestPhase)
|
|
336
|
+
return false;
|
|
337
|
+
return Array.isArray(grantPhase)
|
|
338
|
+
? grantPhase.includes(requestPhase)
|
|
339
|
+
: grantPhase === requestPhase;
|
|
340
|
+
}
|
|
341
|
+
function matchesGenericTarget(target, requestedTarget) {
|
|
342
|
+
if (!target)
|
|
343
|
+
return true;
|
|
344
|
+
if (!requestedTarget)
|
|
345
|
+
return false;
|
|
346
|
+
const targets = Array.isArray(target) ? target : [target];
|
|
347
|
+
return targets.some((item) => typeof item === 'string'
|
|
348
|
+
? matchesMcpPattern(requestedTarget, item)
|
|
349
|
+
: matchesMcpPattern(requestedTarget, item.pattern));
|
|
350
|
+
}
|
|
351
|
+
function raiseRisk(risk) {
|
|
352
|
+
if (risk === 'low')
|
|
353
|
+
return 'medium';
|
|
354
|
+
return 'high';
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=grants.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function matchesMcpPattern(value, pattern) {
|
|
2
|
+
return matchesUriRule(value, pattern);
|
|
3
|
+
}
|
|
4
|
+
export function evaluateMcpUriPolicy(uri, rules) {
|
|
5
|
+
if (rules.length === 0) {
|
|
6
|
+
return {
|
|
7
|
+
allowed: false,
|
|
8
|
+
reason: 'MCP_RESOURCE_URI_DENIED_NO_RULES',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
for (const rule of rules) {
|
|
12
|
+
const normalized = normalizeUriRule(rule);
|
|
13
|
+
if (matchesUriRule(uri, normalized)) {
|
|
14
|
+
return {
|
|
15
|
+
allowed: true,
|
|
16
|
+
reason: `MCP_RESOURCE_URI_ALLOWED_${normalized.kind.toUpperCase()}`,
|
|
17
|
+
matchedPattern: normalized.pattern,
|
|
18
|
+
matchKind: normalized.kind,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
allowed: false,
|
|
24
|
+
reason: 'MCP_RESOURCE_URI_DENIED_NO_MATCH',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function matchesUriRule(uri, rule) {
|
|
28
|
+
const normalized = normalizeUriRule(rule);
|
|
29
|
+
if (normalized.kind === 'exact')
|
|
30
|
+
return uri === normalized.pattern;
|
|
31
|
+
if (normalized.kind === 'prefix')
|
|
32
|
+
return uri.startsWith(normalized.pattern);
|
|
33
|
+
return globLikeToRegExp(normalized.pattern).test(uri);
|
|
34
|
+
}
|
|
35
|
+
export function normalizeUriRule(rule) {
|
|
36
|
+
if (typeof rule === 'string') {
|
|
37
|
+
return { pattern: rule, kind: inferUriRuleKind(rule) };
|
|
38
|
+
}
|
|
39
|
+
return { pattern: rule.pattern, kind: rule.kind ?? inferUriRuleKind(rule.pattern) };
|
|
40
|
+
}
|
|
41
|
+
export class McpUriPolicy {
|
|
42
|
+
isAllowed(uri, patterns) {
|
|
43
|
+
return evaluateMcpUriPolicy(uri, patterns).allowed;
|
|
44
|
+
}
|
|
45
|
+
decide(uri, patterns) {
|
|
46
|
+
return evaluateMcpUriPolicy(uri, patterns);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function inferUriRuleKind(pattern) {
|
|
50
|
+
if (pattern.includes('*'))
|
|
51
|
+
return 'glob';
|
|
52
|
+
if (pattern.endsWith('/'))
|
|
53
|
+
return 'prefix';
|
|
54
|
+
return 'exact';
|
|
55
|
+
}
|
|
56
|
+
function globLikeToRegExp(pattern) {
|
|
57
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
58
|
+
return new RegExp(`^${escaped}$`);
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=uri-policy.js.map
|