@sentropic/artifacts 0.1.3 → 0.1.4

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,272 @@
1
+ import { TEXT_MIME_PATTERN } from './http.js';
2
+ export const ARTIFACTS_PROVIDER_ID = 'sentropic.artifacts';
3
+ export const ARTIFACTS_APP_ID = 'sentropic-artifacts';
4
+ export const ARTIFACTS_PROVIDER_VERSION = '0.1.4';
5
+ export const ARTIFACTS_TOOL_NAMES = [
6
+ 'artifacts_get_template', 'artifacts_get_api_guide', 'artifacts_ensure',
7
+ 'artifacts_publish', 'artifacts_create_upload', 'artifacts_commit_upload',
8
+ 'artifacts_list', 'artifacts_get', 'artifacts_get_source', 'artifacts_fork',
9
+ 'artifacts_set_access', 'artifacts_promote', 'artifacts_renew',
10
+ 'artifacts_delete', 'artifacts_restore',
11
+ ];
12
+ const str = { type: 'string', minLength: 1 };
13
+ const int = (minimum, maximum) => ({
14
+ type: 'integer', minimum, ...(maximum === undefined ? {} : { maximum }),
15
+ });
16
+ const obj = (properties, required = []) => ({
17
+ type: 'object', properties, required, additionalProperties: false,
18
+ });
19
+ const record = {
20
+ type: 'object',
21
+ additionalProperties: { type: ['string', 'number', 'boolean', 'null'] },
22
+ };
23
+ const fileBytes = { type: 'array', items: int(0, 255) };
24
+ const fileEntry = {
25
+ ...obj({ path: str, mimeType: str, bytes: fileBytes, text: { type: 'string', description: 'UTF-8 text; mutually exclusive with bytes; text MIME types only.' } }, ['path', 'mimeType']),
26
+ oneOf: [
27
+ { required: ['bytes'], not: { required: ['text'] } },
28
+ { required: ['text'], not: { required: ['bytes'] }, properties: { mimeType: { pattern: TEXT_MIME_PATTERN } } },
29
+ ],
30
+ };
31
+ const sourceRef = obj({
32
+ kind: { type: 'string', enum: ['inline-original', 'base-commit', 'recipe'] },
33
+ commit: { type: 'string' },
34
+ steps: { type: 'array', items: { type: 'string' } },
35
+ }, ['kind']);
36
+ const idOnly = obj({ artifactId: str }, ['artifactId']);
37
+ export const ARTIFACTS_TOOL_SPECS = [
38
+ { name: 'artifacts_get_template', description: 'Get an approved template kit (sources + build recipe).', inputSchema: obj({ templateId: str }, ['templateId']), mutability: 'read-only' },
39
+ { name: 'artifacts_get_api_guide', description: 'Get the versioned artifacts API guide.', inputSchema: obj({ version: str }), mutability: 'read-only' },
40
+ { name: 'artifacts_ensure', description: 'Resolve or create the private artifact view for a binding.', inputSchema: obj({ bindingName: str, storageScopeId: str, idempotencyKey: str, paramsHash: str, parameters: record }, ['bindingName', 'idempotencyKey']), mutability: 'state-transition', idempotent: true },
41
+ { name: 'artifacts_publish', description: 'Publish inline files as a new release.', inputSchema: obj({ artifactId: str, files: { type: 'array', minItems: 1, items: fileEntry }, profile: record, source: sourceRef, idempotencyKey: str, paramsHash: str }, ['artifactId', 'files']), mutability: 'append', idempotent: true },
42
+ { name: 'artifacts_create_upload', description: 'Open a bounded upload session for large bytes.', inputSchema: obj({ artifactId: str, mimeType: str, maxSize: int(1) }, ['artifactId', 'mimeType']), mutability: 'state-transition' },
43
+ { name: 'artifacts_commit_upload', description: 'Verify staged bytes and publish them as a release.', inputSchema: obj({ artifactId: str, uploadId: str, path: str, mimeType: str, digest: str, size: int(0), profile: record, idempotencyKey: str, paramsHash: str }, ['artifactId', 'uploadId', 'path', 'mimeType', 'digest', 'size']), mutability: 'append', idempotent: true },
44
+ { name: 'artifacts_list', description: 'List artifacts visible to the caller (ACL before pagination).', inputSchema: obj({ limit: int(1, 100), offset: int(0) }), mutability: 'read-only' },
45
+ { name: 'artifacts_get', description: 'Get artifact metadata (grants visible to the owner only).', inputSchema: idOnly, mutability: 'read-only' },
46
+ { name: 'artifacts_get_source', description: 'Export the attached source reference (separate right).', inputSchema: idOnly, mutability: 'read-only' },
47
+ { name: 'artifacts_fork', description: 'Fork privately under a fresh identity (no ACL copied).', inputSchema: idOnly, mutability: 'state-transition' },
48
+ { name: 'artifacts_set_access', description: 'Grant or revoke read/source access, or toggle public mode.', inputSchema: obj({ artifactId: str, action: { type: 'string', enum: ['grant', 'revoke', 'set-public'] }, principalId: str, grant: { type: 'string', enum: ['read', 'source'] }, isPublic: { type: 'boolean' } }, ['artifactId', 'action']), mutability: 'patch', confirm: true },
49
+ { name: 'artifacts_promote', description: 'Select a release explicitly (expected-current CAS).', inputSchema: obj({ artifactId: str, releaseId: str, expectedCurrent: str }, ['artifactId', 'releaseId', 'expectedCurrent']), mutability: 'patch' },
50
+ { name: 'artifacts_renew', description: 'Renew the artifact retention timestamp.', inputSchema: idOnly, mutability: 'patch' },
51
+ { name: 'artifacts_delete', description: 'Withdraw into pending deletion (undo via artifacts_restore).', inputSchema: idOnly, mutability: 'delete', confirm: true },
52
+ { name: 'artifacts_restore', description: 'Cancel a pending deletion or withdrawal before purge.', inputSchema: idOnly, mutability: 'state-transition' },
53
+ ];
54
+ /** Kernel provider parameter schema (typed non-secret host settings). */
55
+ export const ARTIFACTS_PARAMETER_SCHEMA = {
56
+ properties: {
57
+ defaultStorageScopeId: { type: 'string', minLength: 1, description: 'Fallback storage scope for artifacts_ensure.' },
58
+ uploadBudgetBytes: { type: 'integer', minimum: 1, description: 'Declared bounded-upload budget.' },
59
+ },
60
+ required: [],
61
+ };
62
+ /** Build the sentropic.artifacts manifest (host scope bound pre-freeze). */
63
+ export function createArtifactsManifest(options = {}) {
64
+ const scopes = options.invocationScope === undefined ? [] : [options.invocationScope];
65
+ const tools = ARTIFACTS_TOOL_SPECS.map((spec) => ({
66
+ kind: 'tool',
67
+ name: spec.name,
68
+ description: spec.description,
69
+ requiredScopes: [...scopes],
70
+ requiredClaims: [],
71
+ inputSchema: spec.inputSchema,
72
+ outputSchema: { type: 'object' },
73
+ redactionClass: 'low',
74
+ mutability: spec.mutability,
75
+ mutatesExternalSystem: spec.mutability !== 'read-only',
76
+ idempotency: spec.idempotent === true
77
+ ? { required: true, scope: 'principal' }
78
+ : { required: false },
79
+ gates: {
80
+ requiresElicitation: false,
81
+ requiresHumanConfirmation: spec.confirm === true,
82
+ requiresPrincipalGate: false,
83
+ },
84
+ }));
85
+ return {
86
+ appId: ARTIFACTS_APP_ID,
87
+ providerId: ARTIFACTS_PROVIDER_ID,
88
+ version: ARTIFACTS_PROVIDER_VERSION,
89
+ displayName: options.displayName ?? 'Sentropic Artifacts',
90
+ resources: [],
91
+ tools,
92
+ prompts: [],
93
+ authz: { requiredClaims: [], scopes: [...scopes], tenantResolution: 'custom-resolver' },
94
+ audit: { eventKinds: ['invoke'] },
95
+ durability: {},
96
+ };
97
+ }
98
+ function needString(args, name) {
99
+ const value = args[name];
100
+ if (typeof value !== 'string' || value.length === 0) {
101
+ throw new Error(`invalid request: missing ${name}`);
102
+ }
103
+ return value;
104
+ }
105
+ function optString(args, name) {
106
+ const value = args[name];
107
+ return typeof value === 'string' && value.length !== 0 ? value : undefined;
108
+ }
109
+ /** Create the 15-tool executor over any conforming artifacts HTTP app. */
110
+ export function createArtifactsToolExecutor(deps) {
111
+ const call = (actorId, method, path, body, storageScopeId) => deps.app.handle({
112
+ method, path, actorId, body, storageScopeId: storageScopeId ?? deps.defaultStorageScopeId,
113
+ });
114
+ function setAccess(actorId, args) {
115
+ const artifactId = needString(args, 'artifactId');
116
+ const action = needString(args, 'action');
117
+ if (action === 'grant') {
118
+ const grant = needString(args, 'grant');
119
+ if (grant !== 'read' && grant !== 'source') {
120
+ throw new Error('invalid request: grant must be read or source');
121
+ }
122
+ return call(actorId, 'POST', '/access/grant', {
123
+ artifactId, principalId: needString(args, 'principalId'), grant,
124
+ });
125
+ }
126
+ if (action === 'revoke') {
127
+ return call(actorId, 'POST', '/access/revoke', {
128
+ artifactId, principalId: needString(args, 'principalId'),
129
+ });
130
+ }
131
+ if (action === 'set-public') {
132
+ if (typeof args['isPublic'] !== 'boolean') {
133
+ throw new Error('invalid request: isPublic must be a boolean');
134
+ }
135
+ return call(actorId, 'POST', '/access/public', { artifactId, isPublic: args['isPublic'] });
136
+ }
137
+ throw new Error(`invalid request: unknown set-access action ${action}`);
138
+ }
139
+ return {
140
+ async invoke(input) {
141
+ const { actorId, args } = input;
142
+ switch (input.tool) {
143
+ case 'artifacts_get_template':
144
+ return call(actorId, 'GET', `/templates/${needString(args, 'templateId')}`);
145
+ case 'artifacts_get_api_guide': {
146
+ const version = optString(args, 'version');
147
+ return call(actorId, 'GET', version === undefined ? '/api-guide' : `/api-guide/${version}`);
148
+ }
149
+ case 'artifacts_ensure':
150
+ return call(actorId, 'POST', '/ensure', {
151
+ bindingName: needString(args, 'bindingName'),
152
+ idempotencyKey: needString(args, 'idempotencyKey'),
153
+ paramsHash: optString(args, 'paramsHash') ?? '',
154
+ parameters: args['parameters'] ?? {},
155
+ }, input.storageScopeId);
156
+ case 'artifacts_publish':
157
+ return call(actorId, 'POST', '/publish', {
158
+ artifactId: needString(args, 'artifactId'),
159
+ files: args['files'],
160
+ profile: args['profile'] ?? {},
161
+ source: args['source'],
162
+ idempotencyKey: optString(args, 'idempotencyKey'),
163
+ paramsHash: optString(args, 'paramsHash'),
164
+ });
165
+ case 'artifacts_create_upload':
166
+ return call(actorId, 'POST', '/uploads', {
167
+ artifactId: needString(args, 'artifactId'),
168
+ mimeType: needString(args, 'mimeType'),
169
+ maxSize: args['maxSize'],
170
+ });
171
+ case 'artifacts_commit_upload':
172
+ return call(actorId, 'POST', '/uploads/commit', {
173
+ artifactId: needString(args, 'artifactId'),
174
+ uploadId: needString(args, 'uploadId'),
175
+ path: needString(args, 'path'),
176
+ mimeType: needString(args, 'mimeType'),
177
+ digest: needString(args, 'digest'),
178
+ size: args['size'],
179
+ profile: args['profile'] ?? {},
180
+ idempotencyKey: optString(args, 'idempotencyKey'),
181
+ paramsHash: optString(args, 'paramsHash'),
182
+ });
183
+ case 'artifacts_list':
184
+ return call(actorId, 'POST', '/list', { limit: args['limit'], offset: args['offset'] });
185
+ case 'artifacts_get':
186
+ return call(actorId, 'GET', `/meta/${needString(args, 'artifactId')}`);
187
+ case 'artifacts_get_source':
188
+ return call(actorId, 'GET', `/source/${needString(args, 'artifactId')}`);
189
+ case 'artifacts_fork':
190
+ return call(actorId, 'POST', '/fork', { artifactId: needString(args, 'artifactId') });
191
+ case 'artifacts_set_access':
192
+ return setAccess(actorId, args);
193
+ case 'artifacts_promote':
194
+ return call(actorId, 'POST', '/promote', {
195
+ artifactId: needString(args, 'artifactId'),
196
+ releaseId: needString(args, 'releaseId'),
197
+ expectedCurrent: needString(args, 'expectedCurrent'),
198
+ });
199
+ case 'artifacts_renew':
200
+ return call(actorId, 'POST', '/renew', { artifactId: needString(args, 'artifactId') });
201
+ case 'artifacts_delete':
202
+ return call(actorId, 'POST', '/delete', { artifactId: needString(args, 'artifactId') });
203
+ case 'artifacts_restore':
204
+ return call(actorId, 'POST', '/restore', { artifactId: needString(args, 'artifactId') });
205
+ default:
206
+ throw new Error(`unknown artifacts tool: ${input.tool}`);
207
+ }
208
+ },
209
+ };
210
+ }
211
+ function errorCodeOf(body) {
212
+ if (typeof body === 'object' && body !== null) {
213
+ const code = body['error'];
214
+ if (typeof code === 'string' && code.length !== 0)
215
+ return code;
216
+ }
217
+ return 'internal_error';
218
+ }
219
+ /** Create the platform adapter driving the 15 tools over the shared app. */
220
+ export function createArtifactsAdapter(options) {
221
+ const manifest = createArtifactsManifest(options);
222
+ const executor = createArtifactsToolExecutor({
223
+ app: options.app,
224
+ defaultStorageScopeId: options.defaultStorageScopeId,
225
+ });
226
+ return {
227
+ appId: ARTIFACTS_APP_ID,
228
+ connectorId: ARTIFACTS_PROVIDER_ID,
229
+ manifest,
230
+ resolveTenant: (input) => Promise.resolve({
231
+ principalRef: input.principalSub,
232
+ tenantRef: input.tenantRef,
233
+ connectorInstanceId: input.connectorInstanceId,
234
+ ...(input.workspaceRef === undefined ? {} : { workspaceRef: input.workspaceRef }),
235
+ }),
236
+ listCapabilities: () => Promise.resolve([...manifest.tools]),
237
+ async invokeTool(req) {
238
+ const auditId = req.ctx.auditId;
239
+ try {
240
+ const args = typeof req.input === 'object' && req.input !== null && !Array.isArray(req.input)
241
+ ? req.input
242
+ : {};
243
+ const res = await executor.invoke({
244
+ tool: req.capabilityRef,
245
+ actorId: req.ctx.principal.sub,
246
+ args,
247
+ });
248
+ if (res.status >= 200 && res.status < 300) {
249
+ return { ok: true, output: res.body, auditId, redactionClass: 'low' };
250
+ }
251
+ const code = errorCodeOf(res.body);
252
+ return {
253
+ ok: false, auditId, redactionClass: 'low',
254
+ error: { code, message: code, retriable: res.status >= 500 },
255
+ };
256
+ }
257
+ catch {
258
+ return {
259
+ ok: false, auditId, redactionClass: 'low',
260
+ error: { code: 'invalid_request', message: 'invalid_request', retriable: false },
261
+ };
262
+ }
263
+ },
264
+ readResource: (req) => Promise.resolve({
265
+ ok: false,
266
+ auditId: req.ctx.auditId,
267
+ redactionClass: 'low',
268
+ error: { code: 'not_found', message: 'not_found', retriable: false },
269
+ }),
270
+ validateSecrets: () => Promise.resolve([]),
271
+ };
272
+ }
package/dist/mcp.d.ts CHANGED
@@ -1,62 +1,7 @@
1
- /**
2
- * L-5e — reusable artifacts MCP provider (spec-v8 §9 5e, review B3).
3
- *
4
- * Part 1: identity, the 15 exact `artifacts_*` tool specs with closed input
5
- * schemas, and the platform manifest. The logical provider ID is
6
- * `sentropic.artifacts`; the generic provider hardcodes no application
7
- * OAuth vocabulary — a host binds its invocation scope through the
8
- * manifest factory before digests freeze (§3.5). Part 2 adds the tool
9
- * executor over the same HTTP app direct calls use; part 3 adds the
10
- * platform adapter and the kernel registry definition (§3).
11
- */
12
- import type { AppConnectorProviderAdapter, AppMcpProviderManifest, CapabilityTool } from '@sentropic/mcp-platform';
13
- import type { ProviderParameterSchema, RegistryDefinition } from '@sentropic/mcp-platform/registry';
14
- import type { HttpRequest, HttpResponse } from './http.js';
15
- export declare const ARTIFACTS_PROVIDER_ID = "sentropic.artifacts";
16
- export declare const ARTIFACTS_APP_ID = "sentropic-artifacts";
17
- export declare const ARTIFACTS_PROVIDER_VERSION = "0.1.2";
18
- export declare const ARTIFACTS_TOOL_NAMES: readonly ["artifacts_get_template", "artifacts_get_api_guide", "artifacts_ensure", "artifacts_publish", "artifacts_create_upload", "artifacts_commit_upload", "artifacts_list", "artifacts_get", "artifacts_get_source", "artifacts_fork", "artifacts_set_access", "artifacts_promote", "artifacts_renew", "artifacts_delete", "artifacts_restore"];
19
- export type ArtifactsToolName = (typeof ARTIFACTS_TOOL_NAMES)[number];
20
- type Schema = Record<string, unknown>;
21
- interface ToolSpec {
22
- name: ArtifactsToolName;
23
- description: string;
24
- inputSchema: Schema;
25
- mutability: CapabilityTool['mutability'];
26
- idempotent?: boolean;
27
- confirm?: boolean;
28
- }
29
- export declare const ARTIFACTS_TOOL_SPECS: readonly ToolSpec[];
30
- /** Kernel provider parameter schema (typed non-secret host settings). */
31
- export declare const ARTIFACTS_PARAMETER_SCHEMA: ProviderParameterSchema;
32
- export interface ArtifactsManifestOptions {
33
- /** Host authorization profile: scope bound to every tool invocation. */
34
- invocationScope?: string;
35
- displayName?: string;
36
- }
37
- /** Build the sentropic.artifacts manifest (host scope bound pre-freeze). */
38
- export declare function createArtifactsManifest(options?: ArtifactsManifestOptions): AppMcpProviderManifest;
39
- export interface ArtifactsApp {
40
- handle(request: HttpRequest): Promise<HttpResponse>;
41
- }
42
- export interface ArtifactsInvokeInput {
43
- tool: string;
44
- actorId: string;
45
- args: Record<string, unknown>;
46
- }
47
- /** Create the 15-tool executor over any conforming artifacts HTTP app. */
48
- export declare function createArtifactsToolExecutor(deps: {
49
- app: ArtifactsApp;
50
- defaultStorageScopeId?: string;
51
- }): {
52
- invoke(input: ArtifactsInvokeInput): Promise<HttpResponse>;
53
- };
54
- export interface ArtifactsAdapterOptions extends ArtifactsManifestOptions {
55
- app: ArtifactsApp;
56
- defaultStorageScopeId?: string;
57
- }
58
- /** Create the platform adapter driving the 15 tools over the shared app. */
59
- export declare function createArtifactsAdapter(options: ArtifactsAdapterOptions): AppConnectorProviderAdapter;
1
+ /** Platform registry wrapper. Use /mcp-core when the platform is absent. */
2
+ export * from './mcp-core.js';
3
+ import type { RegistryDefinition } from '@sentropic/mcp-platform/registry';
4
+ import { type ArtifactsAdapterOptions } from './mcp-core.js';
60
5
  export interface ArtifactsDefinitionOptions extends ArtifactsAdapterOptions {
61
6
  /** Local config-review reference (integrity metadata, never authority). */
62
7
  approvalRef?: string;
@@ -65,4 +10,3 @@ export interface ArtifactsDefinitionOptions extends ArtifactsAdapterOptions {
65
10
  }
66
11
  /** Build the kernel registry definition for sentropic.artifacts (§3). */
67
12
  export declare function createArtifactsRegistryDefinition(options: ArtifactsDefinitionOptions): RegistryDefinition;
68
- export {};
package/dist/mcp.js CHANGED
@@ -1,276 +1,7 @@
1
+ /** Platform registry wrapper. Use /mcp-core when the platform is absent. */
2
+ export * from './mcp-core.js';
1
3
  import { computeManifestDigest } from '@sentropic/mcp-platform/tool-configuration';
2
- import { TEXT_MIME_PATTERN } from './http.js';
3
- export const ARTIFACTS_PROVIDER_ID = 'sentropic.artifacts';
4
- export const ARTIFACTS_APP_ID = 'sentropic-artifacts';
5
- export const ARTIFACTS_PROVIDER_VERSION = '0.1.2';
6
- export const ARTIFACTS_TOOL_NAMES = [
7
- 'artifacts_get_template', 'artifacts_get_api_guide', 'artifacts_ensure',
8
- 'artifacts_publish', 'artifacts_create_upload', 'artifacts_commit_upload',
9
- 'artifacts_list', 'artifacts_get', 'artifacts_get_source', 'artifacts_fork',
10
- 'artifacts_set_access', 'artifacts_promote', 'artifacts_renew',
11
- 'artifacts_delete', 'artifacts_restore',
12
- ];
13
- const str = { type: 'string', minLength: 1 };
14
- const int = (minimum, maximum) => ({
15
- type: 'integer', minimum, ...(maximum === undefined ? {} : { maximum }),
16
- });
17
- const obj = (properties, required = []) => ({
18
- type: 'object', properties, required, additionalProperties: false,
19
- });
20
- const record = {
21
- type: 'object',
22
- additionalProperties: { type: ['string', 'number', 'boolean', 'null'] },
23
- };
24
- const fileBytes = { type: 'array', items: int(0, 255) };
25
- const fileEntry = {
26
- ...obj({ path: str, mimeType: str, bytes: fileBytes, text: { type: 'string', description: 'UTF-8 text; mutually exclusive with bytes; text MIME types only.' } }, ['path', 'mimeType']),
27
- oneOf: [
28
- { required: ['bytes'], not: { required: ['text'] } },
29
- { required: ['text'], not: { required: ['bytes'] }, properties: { mimeType: { pattern: TEXT_MIME_PATTERN } } },
30
- ],
31
- };
32
- const sourceRef = obj({
33
- kind: { type: 'string', enum: ['inline-original', 'base-commit', 'recipe'] },
34
- commit: { type: 'string' },
35
- steps: { type: 'array', items: { type: 'string' } },
36
- }, ['kind']);
37
- const idOnly = obj({ artifactId: str }, ['artifactId']);
38
- export const ARTIFACTS_TOOL_SPECS = [
39
- { name: 'artifacts_get_template', description: 'Get an approved template kit (sources + build recipe).', inputSchema: obj({ templateId: str }, ['templateId']), mutability: 'read-only' },
40
- { name: 'artifacts_get_api_guide', description: 'Get the versioned artifacts API guide.', inputSchema: obj({ version: str }), mutability: 'read-only' },
41
- { name: 'artifacts_ensure', description: 'Resolve or create the private artifact view for a binding.', inputSchema: obj({ bindingName: str, storageScopeId: str, idempotencyKey: str, paramsHash: str, parameters: record }, ['bindingName', 'idempotencyKey']), mutability: 'state-transition', idempotent: true },
42
- { name: 'artifacts_publish', description: 'Publish inline files as a new release.', inputSchema: obj({ artifactId: str, files: { type: 'array', minItems: 1, items: fileEntry }, profile: record, source: sourceRef, idempotencyKey: str, paramsHash: str }, ['artifactId', 'files']), mutability: 'append', idempotent: true },
43
- { name: 'artifacts_create_upload', description: 'Open a bounded upload session for large bytes.', inputSchema: obj({ artifactId: str, mimeType: str, maxSize: int(1) }, ['artifactId', 'mimeType']), mutability: 'state-transition' },
44
- { name: 'artifacts_commit_upload', description: 'Verify staged bytes and publish them as a release.', inputSchema: obj({ artifactId: str, uploadId: str, path: str, mimeType: str, digest: str, size: int(0), profile: record, idempotencyKey: str, paramsHash: str }, ['artifactId', 'uploadId', 'path', 'mimeType', 'digest', 'size']), mutability: 'append', idempotent: true },
45
- { name: 'artifacts_list', description: 'List artifacts visible to the caller (ACL before pagination).', inputSchema: obj({ limit: int(1, 100), offset: int(0) }), mutability: 'read-only' },
46
- { name: 'artifacts_get', description: 'Get artifact metadata (grants visible to the owner only).', inputSchema: idOnly, mutability: 'read-only' },
47
- { name: 'artifacts_get_source', description: 'Export the attached source reference (separate right).', inputSchema: idOnly, mutability: 'read-only' },
48
- { name: 'artifacts_fork', description: 'Fork privately under a fresh identity (no ACL copied).', inputSchema: idOnly, mutability: 'state-transition' },
49
- { name: 'artifacts_set_access', description: 'Grant or revoke read/source access, or toggle public mode.', inputSchema: obj({ artifactId: str, action: { type: 'string', enum: ['grant', 'revoke', 'set-public'] }, principalId: str, grant: { type: 'string', enum: ['read', 'source'] }, isPublic: { type: 'boolean' } }, ['artifactId', 'action']), mutability: 'patch', confirm: true },
50
- { name: 'artifacts_promote', description: 'Select a release explicitly (expected-current CAS).', inputSchema: obj({ artifactId: str, releaseId: str, expectedCurrent: str }, ['artifactId', 'releaseId', 'expectedCurrent']), mutability: 'patch' },
51
- { name: 'artifacts_renew', description: 'Renew the artifact retention timestamp.', inputSchema: idOnly, mutability: 'patch' },
52
- { name: 'artifacts_delete', description: 'Withdraw into pending deletion (undo via artifacts_restore).', inputSchema: idOnly, mutability: 'delete', confirm: true },
53
- { name: 'artifacts_restore', description: 'Cancel a pending deletion or withdrawal before purge.', inputSchema: idOnly, mutability: 'state-transition' },
54
- ];
55
- /** Kernel provider parameter schema (typed non-secret host settings). */
56
- export const ARTIFACTS_PARAMETER_SCHEMA = {
57
- properties: {
58
- defaultStorageScopeId: { type: 'string', minLength: 1, description: 'Fallback storage scope for artifacts_ensure.' },
59
- uploadBudgetBytes: { type: 'integer', minimum: 1, description: 'Declared bounded-upload budget.' },
60
- },
61
- required: [],
62
- };
63
- /** Build the sentropic.artifacts manifest (host scope bound pre-freeze). */
64
- export function createArtifactsManifest(options = {}) {
65
- const scopes = options.invocationScope === undefined ? [] : [options.invocationScope];
66
- const tools = ARTIFACTS_TOOL_SPECS.map((spec) => ({
67
- kind: 'tool',
68
- name: spec.name,
69
- description: spec.description,
70
- requiredScopes: [...scopes],
71
- requiredClaims: [],
72
- inputSchema: spec.inputSchema,
73
- outputSchema: { type: 'object' },
74
- redactionClass: 'low',
75
- mutability: spec.mutability,
76
- mutatesExternalSystem: spec.mutability !== 'read-only',
77
- idempotency: spec.idempotent === true
78
- ? { required: true, scope: 'principal' }
79
- : { required: false },
80
- gates: {
81
- requiresElicitation: false,
82
- requiresHumanConfirmation: spec.confirm === true,
83
- requiresPrincipalGate: false,
84
- },
85
- }));
86
- return {
87
- appId: ARTIFACTS_APP_ID,
88
- providerId: ARTIFACTS_PROVIDER_ID,
89
- version: ARTIFACTS_PROVIDER_VERSION,
90
- displayName: options.displayName ?? 'Sentropic Artifacts',
91
- resources: [],
92
- tools,
93
- prompts: [],
94
- authz: { requiredClaims: [], scopes: [...scopes], tenantResolution: 'custom-resolver' },
95
- audit: { eventKinds: ['invoke'] },
96
- durability: {},
97
- };
98
- }
99
- function needString(args, name) {
100
- const value = args[name];
101
- if (typeof value !== 'string' || value.length === 0) {
102
- throw new Error(`invalid request: missing ${name}`);
103
- }
104
- return value;
105
- }
106
- function optString(args, name) {
107
- const value = args[name];
108
- return typeof value === 'string' && value.length !== 0 ? value : undefined;
109
- }
110
- /** Create the 15-tool executor over any conforming artifacts HTTP app. */
111
- export function createArtifactsToolExecutor(deps) {
112
- const call = (actorId, method, path, body) => deps.app.handle({ method, path, actorId, body });
113
- function setAccess(actorId, args) {
114
- const artifactId = needString(args, 'artifactId');
115
- const action = needString(args, 'action');
116
- if (action === 'grant') {
117
- const grant = needString(args, 'grant');
118
- if (grant !== 'read' && grant !== 'source') {
119
- throw new Error('invalid request: grant must be read or source');
120
- }
121
- return call(actorId, 'POST', '/access/grant', {
122
- artifactId, principalId: needString(args, 'principalId'), grant,
123
- });
124
- }
125
- if (action === 'revoke') {
126
- return call(actorId, 'POST', '/access/revoke', {
127
- artifactId, principalId: needString(args, 'principalId'),
128
- });
129
- }
130
- if (action === 'set-public') {
131
- if (typeof args['isPublic'] !== 'boolean') {
132
- throw new Error('invalid request: isPublic must be a boolean');
133
- }
134
- return call(actorId, 'POST', '/access/public', { artifactId, isPublic: args['isPublic'] });
135
- }
136
- throw new Error(`invalid request: unknown set-access action ${action}`);
137
- }
138
- return {
139
- async invoke(input) {
140
- const { actorId, args } = input;
141
- switch (input.tool) {
142
- case 'artifacts_get_template':
143
- return call(actorId, 'GET', `/templates/${needString(args, 'templateId')}`);
144
- case 'artifacts_get_api_guide': {
145
- const version = optString(args, 'version');
146
- return call(actorId, 'GET', version === undefined ? '/api-guide' : `/api-guide/${version}`);
147
- }
148
- case 'artifacts_ensure':
149
- return call(actorId, 'POST', '/ensure', {
150
- bindingName: needString(args, 'bindingName'),
151
- storageScopeId: optString(args, 'storageScopeId') ?? deps.defaultStorageScopeId
152
- ?? needString(args, 'storageScopeId'),
153
- idempotencyKey: needString(args, 'idempotencyKey'),
154
- paramsHash: optString(args, 'paramsHash') ?? '',
155
- parameters: args['parameters'] ?? {},
156
- });
157
- case 'artifacts_publish':
158
- return call(actorId, 'POST', '/publish', {
159
- artifactId: needString(args, 'artifactId'),
160
- files: args['files'],
161
- profile: args['profile'] ?? {},
162
- source: args['source'],
163
- idempotencyKey: optString(args, 'idempotencyKey'),
164
- paramsHash: optString(args, 'paramsHash'),
165
- });
166
- case 'artifacts_create_upload':
167
- return call(actorId, 'POST', '/uploads', {
168
- artifactId: needString(args, 'artifactId'),
169
- mimeType: needString(args, 'mimeType'),
170
- maxSize: args['maxSize'],
171
- });
172
- case 'artifacts_commit_upload':
173
- return call(actorId, 'POST', '/uploads/commit', {
174
- artifactId: needString(args, 'artifactId'),
175
- uploadId: needString(args, 'uploadId'),
176
- path: needString(args, 'path'),
177
- mimeType: needString(args, 'mimeType'),
178
- digest: needString(args, 'digest'),
179
- size: args['size'],
180
- profile: args['profile'] ?? {},
181
- idempotencyKey: optString(args, 'idempotencyKey'),
182
- paramsHash: optString(args, 'paramsHash'),
183
- });
184
- case 'artifacts_list':
185
- return call(actorId, 'POST', '/list', { limit: args['limit'], offset: args['offset'] });
186
- case 'artifacts_get':
187
- return call(actorId, 'GET', `/meta/${needString(args, 'artifactId')}`);
188
- case 'artifacts_get_source':
189
- return call(actorId, 'GET', `/source/${needString(args, 'artifactId')}`);
190
- case 'artifacts_fork':
191
- return call(actorId, 'POST', '/fork', { artifactId: needString(args, 'artifactId') });
192
- case 'artifacts_set_access':
193
- return setAccess(actorId, args);
194
- case 'artifacts_promote':
195
- return call(actorId, 'POST', '/promote', {
196
- artifactId: needString(args, 'artifactId'),
197
- releaseId: needString(args, 'releaseId'),
198
- expectedCurrent: needString(args, 'expectedCurrent'),
199
- });
200
- case 'artifacts_renew':
201
- return call(actorId, 'POST', '/renew', { artifactId: needString(args, 'artifactId') });
202
- case 'artifacts_delete':
203
- return call(actorId, 'POST', '/delete', { artifactId: needString(args, 'artifactId') });
204
- case 'artifacts_restore':
205
- return call(actorId, 'POST', '/restore', { artifactId: needString(args, 'artifactId') });
206
- default:
207
- throw new Error(`unknown artifacts tool: ${input.tool}`);
208
- }
209
- },
210
- };
211
- }
212
- function errorCodeOf(body) {
213
- if (typeof body === 'object' && body !== null) {
214
- const code = body['error'];
215
- if (typeof code === 'string' && code.length !== 0)
216
- return code;
217
- }
218
- return 'internal_error';
219
- }
220
- /** Create the platform adapter driving the 15 tools over the shared app. */
221
- export function createArtifactsAdapter(options) {
222
- const manifest = createArtifactsManifest(options);
223
- const executor = createArtifactsToolExecutor({
224
- app: options.app,
225
- defaultStorageScopeId: options.defaultStorageScopeId,
226
- });
227
- return {
228
- appId: ARTIFACTS_APP_ID,
229
- connectorId: ARTIFACTS_PROVIDER_ID,
230
- manifest,
231
- resolveTenant: (input) => Promise.resolve({
232
- principalRef: input.principalSub,
233
- tenantRef: input.tenantRef,
234
- connectorInstanceId: input.connectorInstanceId,
235
- ...(input.workspaceRef === undefined ? {} : { workspaceRef: input.workspaceRef }),
236
- }),
237
- listCapabilities: () => Promise.resolve([...manifest.tools]),
238
- async invokeTool(req) {
239
- const auditId = req.ctx.auditId;
240
- try {
241
- const args = typeof req.input === 'object' && req.input !== null && !Array.isArray(req.input)
242
- ? req.input
243
- : {};
244
- const res = await executor.invoke({
245
- tool: req.capabilityRef,
246
- actorId: req.ctx.principal.sub,
247
- args,
248
- });
249
- if (res.status >= 200 && res.status < 300) {
250
- return { ok: true, output: res.body, auditId, redactionClass: 'low' };
251
- }
252
- const code = errorCodeOf(res.body);
253
- return {
254
- ok: false, auditId, redactionClass: 'low',
255
- error: { code, message: code, retriable: res.status >= 500 },
256
- };
257
- }
258
- catch {
259
- return {
260
- ok: false, auditId, redactionClass: 'low',
261
- error: { code: 'invalid_request', message: 'invalid_request', retriable: false },
262
- };
263
- }
264
- },
265
- readResource: (req) => Promise.resolve({
266
- ok: false,
267
- auditId: req.ctx.auditId,
268
- redactionClass: 'low',
269
- error: { code: 'not_found', message: 'not_found', retriable: false },
270
- }),
271
- validateSecrets: () => Promise.resolve([]),
272
- };
273
- }
4
+ import { createArtifactsAdapter, ARTIFACTS_TOOL_SPECS, ARTIFACTS_PARAMETER_SCHEMA, } from './mcp-core.js';
274
5
  const EMPTY_TOOL_CONFIG = { type: 'object', properties: {}, additionalProperties: false };
275
6
  /** Build the kernel registry definition for sentropic.artifacts (§3). */
276
7
  export function createArtifactsRegistryDefinition(options) {
@@ -0,0 +1,10 @@
1
+ import type { PublicationFileInput } from './publication.js';
2
+ export interface PublicationLimits {
3
+ maxFiles: number;
4
+ maxFileBytes: number;
5
+ maxTotalBytes: number;
6
+ }
7
+ export declare const DEFAULT_PUBLICATION_LIMITS: Readonly<PublicationLimits>;
8
+ export declare function publicationLimits(overrides?: Partial<PublicationLimits>): Readonly<PublicationLimits>;
9
+ /** Validate before copying bytes, hashing, idempotency lookup or recording intents. */
10
+ export declare function validatePublicationFiles(files: PublicationFileInput[], limits: Readonly<PublicationLimits>): void;