@sellable/mcp 0.1.619-wip.121.2 → 0.1.619

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.
@@ -0,0 +1,5 @@
1
+ import type { AgentApprovalConsumeInput, AgentApprovalConsumeResult, AgentApprovalEffectPort, AgentApprovalRequestInput, AgentApprovalRequestPort, AgentApprovalRequestResult } from "./agent-tool-policy.js";
2
+ export declare class HttpAgentApprovalEffectPort implements AgentApprovalEffectPort, AgentApprovalRequestPort {
3
+ requestApproval(input: AgentApprovalRequestInput): Promise<AgentApprovalRequestResult>;
4
+ consumeApprovalAndStartEffect(input: AgentApprovalConsumeInput): Promise<AgentApprovalConsumeResult>;
5
+ }
@@ -0,0 +1,30 @@
1
+ import { getApi } from "./api.js";
2
+ export class HttpAgentApprovalEffectPort {
3
+ async requestApproval(input) {
4
+ return getApi().post("/api/v3/sellable-agent/approvals/requests", {
5
+ requesterId: input.requesterId,
6
+ channelId: input.channelId,
7
+ providerRequestId: input.providerRequestId,
8
+ tool: input.tool,
9
+ args: input.arguments,
10
+ }, { workspaceId: null });
11
+ }
12
+ async consumeApprovalAndStartEffect(input) {
13
+ try {
14
+ const result = await getApi().post("/api/v3/sellable-agent/approvals/effects", {
15
+ approvalId: input.approvalId,
16
+ requesterId: input.requesterId,
17
+ channelId: input.channelId,
18
+ providerRequestId: input.providerRequestId,
19
+ tool: input.tool,
20
+ args: input.arguments,
21
+ }, { workspaceId: null });
22
+ return result.effectId
23
+ ? { status: "AUTHORIZED", effectId: result.effectId }
24
+ : { status: "MISSING" };
25
+ }
26
+ catch {
27
+ return { status: "MISSING" };
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,94 @@
1
+ export interface McpAgentServiceContext {
2
+ kind: "agent_service";
3
+ credentialId: string;
4
+ workspaceId: string;
5
+ agentId: string;
6
+ generation: number;
7
+ policyHash: string;
8
+ selectedChannelIds: readonly string[];
9
+ }
10
+ type McpAgentEnvironment = Record<string, string | undefined>;
11
+ export type McpCredentialIntent = "unconfigured" | "human" | "agent_service" | "invalid";
12
+ export type McpCredentialResolution = {
13
+ mode: "human";
14
+ principal: null;
15
+ } | {
16
+ mode: "agent_service";
17
+ principal: McpAgentServiceContext;
18
+ } | {
19
+ mode: "invalid";
20
+ principal: null;
21
+ code: "invalid_agent_service_context";
22
+ };
23
+ export declare function resolveMcpCredentialIntent(input: {
24
+ env?: McpAgentEnvironment;
25
+ getConfig: () => {
26
+ token: string;
27
+ credentialKind?: "human" | "agent_service";
28
+ };
29
+ getCredentialIntent: () => McpCredentialIntent;
30
+ }): McpCredentialResolution;
31
+ export declare function createMcpCredentialBoundary<T extends {
32
+ name: string;
33
+ }>(input: {
34
+ allTools: readonly T[];
35
+ resolve: () => McpCredentialResolution;
36
+ agentAuthorization: {
37
+ listTools(context: {
38
+ principal: McpAgentServiceContext | null;
39
+ }): readonly T[];
40
+ authorizeCall(call: {
41
+ name: string;
42
+ args: unknown;
43
+ context: {
44
+ principal: McpAgentServiceContext | null;
45
+ requesterId?: string | null;
46
+ channelId?: string | null;
47
+ providerRequestId?: string | null;
48
+ approvalId?: string | null;
49
+ };
50
+ }): Promise<unknown>;
51
+ };
52
+ }): {
53
+ listTools(): readonly T[];
54
+ authorizeCall(call: {
55
+ name: string;
56
+ args: unknown;
57
+ requesterId?: string | null;
58
+ channelId?: string | null;
59
+ providerRequestId?: string | null;
60
+ approvalId?: string | null;
61
+ }): Promise<{
62
+ mode: "invalid";
63
+ authorization: {
64
+ allowed: boolean;
65
+ decision: string;
66
+ code: "invalid_agent_service_context";
67
+ };
68
+ context?: undefined;
69
+ } | {
70
+ mode: "human";
71
+ authorization: null;
72
+ context?: undefined;
73
+ } | {
74
+ mode: "agent_service";
75
+ authorization: {
76
+ allowed: false;
77
+ decision: "DENY";
78
+ code: string;
79
+ };
80
+ context?: undefined;
81
+ } | {
82
+ mode: "agent_service";
83
+ authorization: unknown;
84
+ context: {
85
+ principal: McpAgentServiceContext;
86
+ requesterId: string;
87
+ channelId: string;
88
+ providerRequestId: string;
89
+ approvalId: string | null;
90
+ };
91
+ }>;
92
+ };
93
+ export declare function readMcpAgentServiceContext(env?: McpAgentEnvironment, token?: string | null): McpAgentServiceContext | null;
94
+ export {};
@@ -0,0 +1,177 @@
1
+ export function resolveMcpCredentialIntent(input) {
2
+ const env = input.env ?? process.env;
3
+ let intent;
4
+ try {
5
+ intent = input.getCredentialIntent();
6
+ }
7
+ catch {
8
+ return {
9
+ mode: "invalid",
10
+ principal: null,
11
+ code: "invalid_agent_service_context",
12
+ };
13
+ }
14
+ if (intent === "human" || intent === "unconfigured") {
15
+ return { mode: "human", principal: null };
16
+ }
17
+ if (intent !== "agent_service") {
18
+ return {
19
+ mode: "invalid",
20
+ principal: null,
21
+ code: "invalid_agent_service_context",
22
+ };
23
+ }
24
+ try {
25
+ const config = input.getConfig();
26
+ if (config.credentialKind !== "agent_service") {
27
+ return {
28
+ mode: "invalid",
29
+ principal: null,
30
+ code: "invalid_agent_service_context",
31
+ };
32
+ }
33
+ const principal = readMcpAgentServiceContext(env, config.token);
34
+ return principal
35
+ ? { mode: "agent_service", principal }
36
+ : {
37
+ mode: "invalid",
38
+ principal: null,
39
+ code: "invalid_agent_service_context",
40
+ };
41
+ }
42
+ catch {
43
+ return {
44
+ mode: "invalid",
45
+ principal: null,
46
+ code: "invalid_agent_service_context",
47
+ };
48
+ }
49
+ }
50
+ export function createMcpCredentialBoundary(input) {
51
+ return {
52
+ listTools() {
53
+ const resolved = input.resolve();
54
+ if (resolved.mode === "invalid")
55
+ return [];
56
+ if (resolved.mode === "human")
57
+ return input.allTools;
58
+ return input.agentAuthorization.listTools({
59
+ principal: resolved.principal,
60
+ });
61
+ },
62
+ async authorizeCall(call) {
63
+ const resolved = input.resolve();
64
+ if (resolved.mode === "invalid") {
65
+ return {
66
+ mode: "invalid",
67
+ authorization: {
68
+ allowed: false,
69
+ decision: "DENY",
70
+ code: resolved.code,
71
+ },
72
+ };
73
+ }
74
+ if (resolved.mode === "human") {
75
+ return { mode: "human", authorization: null };
76
+ }
77
+ const requesterId = call.requesterId?.trim();
78
+ const channelId = call.channelId?.trim();
79
+ const providerRequestId = call.providerRequestId?.trim();
80
+ if (!requesterId ||
81
+ requesterId.length > 160 ||
82
+ !channelId ||
83
+ channelId.length > 160 ||
84
+ !providerRequestId ||
85
+ providerRequestId.length > 160) {
86
+ return {
87
+ mode: "agent_service",
88
+ authorization: {
89
+ allowed: false,
90
+ decision: "DENY",
91
+ code: "agent_request_context_required",
92
+ },
93
+ };
94
+ }
95
+ if (!resolved.principal.selectedChannelIds.includes(channelId)) {
96
+ return {
97
+ mode: "agent_service",
98
+ authorization: {
99
+ allowed: false,
100
+ decision: "DENY",
101
+ code: "agent_channel_not_selected",
102
+ },
103
+ };
104
+ }
105
+ const context = {
106
+ principal: resolved.principal,
107
+ requesterId,
108
+ channelId,
109
+ providerRequestId,
110
+ approvalId: call.approvalId?.trim() || null,
111
+ };
112
+ return {
113
+ mode: "agent_service",
114
+ authorization: await input.agentAuthorization.authorizeCall({
115
+ name: call.name,
116
+ args: call.args,
117
+ context,
118
+ }),
119
+ context,
120
+ };
121
+ },
122
+ };
123
+ }
124
+ export function readMcpAgentServiceContext(env = process.env, token) {
125
+ const values = {
126
+ workspaceId: env.SELLABLE_AGENT_WORKSPACE_ID,
127
+ agentId: env.SELLABLE_AGENT_ID,
128
+ generation: env.SELLABLE_AGENT_CREDENTIAL_GENERATION,
129
+ policyHash: env.SELLABLE_AGENT_POLICY_HASH,
130
+ selectedChannelIds: env.SELLABLE_AGENT_SELECTED_CHANNEL_IDS,
131
+ };
132
+ if (Object.values(values).every((value) => value == null)) {
133
+ if (token?.startsWith("sat_")) {
134
+ throw new Error("Sellable Agent service token has no bound Agent context");
135
+ }
136
+ return null;
137
+ }
138
+ const tokenMatch = token?.match(/^sat_([^_\s]+)_(\S+)$/);
139
+ if (!tokenMatch ||
140
+ !values.workspaceId ||
141
+ !values.agentId ||
142
+ !values.generation ||
143
+ !values.policyHash ||
144
+ !values.selectedChannelIds ||
145
+ !/^\d+$/.test(values.generation) ||
146
+ Number(values.generation) < 1 ||
147
+ !/^[a-f0-9]{64}$/.test(values.policyHash) ||
148
+ env.SELLABLE_LOCK_WORKSPACE_ID !== values.workspaceId ||
149
+ !["1", "true", "yes"].includes((env.SELLABLE_REQUIRE_WORKSPACE_LOCK ?? "").trim().toLowerCase())) {
150
+ throw new Error("Incomplete Sellable Agent service context");
151
+ }
152
+ let selectedChannelIds;
153
+ try {
154
+ const parsed = JSON.parse(values.selectedChannelIds);
155
+ if (!Array.isArray(parsed) ||
156
+ parsed.length < 1 ||
157
+ parsed.length > 100 ||
158
+ parsed.some((channelId) => typeof channelId !== "string" ||
159
+ !/^[A-Za-z0-9_-]{1,128}$/.test(channelId)) ||
160
+ new Set(parsed).size !== parsed.length) {
161
+ throw new Error("invalid selected channels");
162
+ }
163
+ selectedChannelIds = [...parsed].sort();
164
+ }
165
+ catch {
166
+ throw new Error("Incomplete Sellable Agent service context");
167
+ }
168
+ return {
169
+ kind: "agent_service",
170
+ credentialId: tokenMatch[1],
171
+ workspaceId: values.workspaceId,
172
+ agentId: values.agentId,
173
+ generation: Number(values.generation),
174
+ policyHash: values.policyHash,
175
+ selectedChannelIds,
176
+ };
177
+ }
@@ -0,0 +1,156 @@
1
+ export type AgentToolDecision = "AUTO" | "REQUESTER_CONFIRM" | "WORKSPACE_APPROVER" | "DENY";
2
+ export interface AgentToolPolicyEntry {
3
+ tool: string;
4
+ version: string;
5
+ decision: AgentToolDecision;
6
+ method: "GET" | "PUT" | "POST";
7
+ route: string;
8
+ handler: string;
9
+ workspaceBinding: string;
10
+ }
11
+ export interface CompiledAgentToolPolicy {
12
+ policyHash: string;
13
+ entries: readonly AgentToolPolicyEntry[];
14
+ }
15
+ export interface AgentPolicyPrincipal {
16
+ kind: "agent_service";
17
+ workspaceId: string;
18
+ agentId: string;
19
+ generation: number;
20
+ policyHash: string;
21
+ }
22
+ export interface AgentApprovalConsumeInput {
23
+ approvalId: string;
24
+ requesterId: string;
25
+ channelId: string;
26
+ providerRequestId: string;
27
+ argumentHash: string;
28
+ workspaceId: string;
29
+ agentId: string;
30
+ tool: string;
31
+ toolVersion: string;
32
+ policyHash: string;
33
+ credentialGeneration: number;
34
+ decision: "REQUESTER_CONFIRM" | "WORKSPACE_APPROVER";
35
+ now: Date;
36
+ arguments?: unknown;
37
+ }
38
+ export type AgentApprovalConsumeResult = {
39
+ status: "AUTHORIZED";
40
+ effectId: string;
41
+ } | {
42
+ status: "MISSING" | "MISMATCH" | "EXPIRED" | "REVOKED" | "CONSUMED";
43
+ };
44
+ export interface AgentApprovalEffectPort {
45
+ consumeApprovalAndStartEffect(input: AgentApprovalConsumeInput): Promise<AgentApprovalConsumeResult>;
46
+ }
47
+ export interface AgentApprovalRequestInput {
48
+ requesterId: string;
49
+ channelId: string;
50
+ providerRequestId: string;
51
+ tool: string;
52
+ arguments: unknown;
53
+ }
54
+ export type AgentApprovalRequestStatus = "PENDING" | "APPROVED" | "DENIED" | "CONSUMED" | "REVOKED" | "EXPIRED";
55
+ export interface AgentApprovalRequestResult {
56
+ approvalId: string;
57
+ workspaceId: string;
58
+ agentId: string;
59
+ requesterId: string;
60
+ channelId: string;
61
+ providerRequestId: string;
62
+ tool: string;
63
+ toolVersion: string;
64
+ decision: "REQUESTER_CONFIRM" | "WORKSPACE_APPROVER";
65
+ status: AgentApprovalRequestStatus;
66
+ expiresAt: string;
67
+ replayed: boolean;
68
+ }
69
+ export interface AgentApprovalRequestPort {
70
+ requestApproval(input: AgentApprovalRequestInput): Promise<AgentApprovalRequestResult>;
71
+ }
72
+ export interface ApprovalBinding {
73
+ approvalId: string;
74
+ actorId: string;
75
+ workspaceId: string;
76
+ agentId: string;
77
+ tool: string;
78
+ toolVersion: string;
79
+ policyHash: string;
80
+ credentialGeneration: number;
81
+ channelId: string;
82
+ argumentHash: string;
83
+ expiresAt: Date;
84
+ consumedAt: Date | null;
85
+ revokedAt: Date | null;
86
+ decision: "REQUESTER_CONFIRM" | "WORKSPACE_APPROVER";
87
+ }
88
+ export interface AgentMcpRequestContext {
89
+ principal: AgentPolicyPrincipal | null;
90
+ requesterId?: string | null;
91
+ channelId?: string | null;
92
+ providerRequestId?: string | null;
93
+ approvalId?: string | null;
94
+ }
95
+ export type AgentToolAuthorization = {
96
+ allowed: true;
97
+ decision: Exclude<AgentToolDecision, "DENY">;
98
+ policy: AgentToolPolicyEntry;
99
+ effectId?: string;
100
+ } | {
101
+ allowed: false;
102
+ decision: AgentToolDecision;
103
+ code: string;
104
+ approval?: AgentApprovalRequestResult;
105
+ };
106
+ export declare const DEFAULT_AGENT_TOOL_CATALOGUE: readonly AgentToolPolicyEntry[];
107
+ export declare function compileAgentToolPolicy(entries?: readonly AgentToolPolicyEntry[]): CompiledAgentToolPolicy;
108
+ export declare function decideAgentTool(compiled: CompiledAgentToolPolicy, tool: string): AgentToolPolicyEntry & {
109
+ allowed: boolean;
110
+ };
111
+ export declare function listToolsForAgent<T extends {
112
+ name: string;
113
+ }>(compiled: CompiledAgentToolPolicy, definitions: readonly T[]): readonly T[];
114
+ export declare function portableSha256(value: string): string;
115
+ export declare function hashApprovalArguments(args: unknown): string;
116
+ export declare function canonicalAgentToolArguments(tool: string, args: unknown): unknown;
117
+ export declare function hashAgentToolArguments(tool: string, args: unknown): string;
118
+ export declare function deriveAgentApprovalRequestId(input: {
119
+ workspaceId: string;
120
+ agentId: string;
121
+ requesterId: string;
122
+ channelId: string;
123
+ providerRequestId: string;
124
+ tool: string;
125
+ toolVersion: string;
126
+ arguments: unknown;
127
+ policyHash: string;
128
+ credentialGeneration: number;
129
+ decision: "REQUESTER_CONFIRM" | "WORKSPACE_APPROVER";
130
+ }): string;
131
+ export declare function validateApprovalBinding(input: {
132
+ approval: ApprovalBinding | null;
133
+ expected: Omit<ApprovalBinding, "approvalId" | "expiresAt" | "consumedAt" | "revokedAt">;
134
+ now?: Date;
135
+ }): {
136
+ ok: boolean;
137
+ code: string;
138
+ };
139
+ export declare function createAgentMcpAuthorizationHandlers(input: {
140
+ allTools: readonly {
141
+ name: string;
142
+ }[];
143
+ policy?: CompiledAgentToolPolicy;
144
+ approvalPort?: (AgentApprovalEffectPort & Partial<AgentApprovalRequestPort>) | null;
145
+ now?: () => Date;
146
+ }): {
147
+ listTools(context: AgentMcpRequestContext): readonly {
148
+ name: string;
149
+ }[];
150
+ authorizeCall(call: {
151
+ name: string;
152
+ args: unknown;
153
+ context: AgentMcpRequestContext;
154
+ }): Promise<AgentToolAuthorization>;
155
+ policy: CompiledAgentToolPolicy;
156
+ };