@happyvertical/smrt-mcp-openai 0.51.38 → 0.52.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/FILES.md ADDED
@@ -0,0 +1,77 @@
1
+ # Optional native files
2
+
3
+ Import browser APIs from `@happyvertical/smrt-mcp-openai/files` and server
4
+ annotation from `@happyvertical/smrt-mcp-openai/files/server`. Neither is loaded
5
+ by ordinary tools. This is a bounded small-file adapter, not a filesystem.
6
+
7
+ `withOpenAiFileEntrypoint(existingWorkflow, ['.txt'])` adds the pinned file
8
+ entrypoint and validates `{file:{name,resourceUri}}`. The existing read workflow
9
+ must resolve the reference through its provider and current trusted principal,
10
+ owner and active tenant. A handle, filename or `_meta["openai/resource"].path`
11
+ is never authority. Remote servers must never read a host path as their own disk.
12
+
13
+ After the portable bridge connects, create `OpenAiFileSession` with its initial
14
+ file input, an existing `authorityTool`, a `fallbackTool`, `maxBytes` (1–16384)
15
+ and explicit MIME allowlist. The authority tool receives `{file,operation}` and
16
+ must reject anonymous, other-owner, other-tenant, guessed, revoked and stale grants
17
+ using the application's existing policy/provider APIs. Successful empty tool
18
+ results mean the application authorized that operation; the adapter does not
19
+ supply a second grant database. No browser-supplied identity is accepted.
20
+
21
+ The host independently enforces the opaque grant. Reads request the selected
22
+ text/blob representation and validate exact URI, MIME and decoded byte count.
23
+ Each operation checks application authority; reads recheck after the host result.
24
+ Writes require a fresh read with `writable:true` and a nonempty ETag, always send
25
+ `ifMatch`, reject parallel writes and never retry. Every outcome, including saved,
26
+ conflict, too-large or unknown upstream failure, requires another authorized read.
27
+ A host ETag is a compare/write guard, not an idempotency token.
28
+
29
+ One bounded subscription refreshes only the initial URI and rechecks authority.
30
+ Refreshes coalesce, revocation disposes the session, and stale/disposed results
31
+ cannot update content. Dispose on UI unmount. Reconnect or a new file needs a new
32
+ session; no handle migration is attempted. Unsubscribe is best effort during
33
+ teardown, while local callbacks and authority end immediately.
34
+
35
+ Absent/unknown resource capability uses the ordinary `fallbackTool` workflow
36
+ for read/import/open/download or editor launch. It receives `{file,operation}`;
37
+ it must return usable text/links/UI through the same application policy. The
38
+ fallback does not silently replay a proposed native write. The user completes
39
+ editing through the ordinary application workflow.
40
+
41
+ `openOpenAiFile` requires explicit application confirmation, an existing provider
42
+ tool resolving the selected object, and an exact trusted `allowedPaths` list. The
43
+ host open request uses only the authorized provider's returned path. It never
44
+ accepts a caller's raw path or reads that path on the server. Without native file
45
+ opening, the provider tool's ordinary open/download result is returned. A remote
46
+ provider should return a normal authorized download workflow, not desktop paths.
47
+
48
+ ## Protocol and evidence
49
+
50
+ Pinned OpenAI source revision `e314720a0daac326217d1f123fcf51647868fa9f`:
51
+ `docs/spec.md`, `typescript/src/shared/file-entrypoint.ts`,
52
+ `typescript/src/app/resources.ts`, `typescript/src/app/files.ts`, and
53
+ `typescript/src/shared/resources.ts`. Full Zod unions define wire fields.
54
+ Native browser capability keys are `experimental["openai/resource"]` and
55
+ `experimental["openai/files"]`, each `{}`; neither has a version field. The
56
+ adapter uses the existing portable bridge's origin/source/lifecycle enforcement.
57
+ There is no SDK-v1 helper, second server, or fabricated server subscription.
58
+ Host resources are intercepted by the browser host; server SDK-v2 HTTP carries
59
+ ordinary principal-authorized entrypoint tools and fallbacks (2026-07-28).
60
+
61
+ Run build, typecheck, test, test:e2e, test:files:e2e, verify:pack and
62
+ verify:files:pack with `pnpm --filter @happyvertical/smrt-mcp-openai` after normal
63
+ install and dependency builds. FILES-TEST-DESIGN.md maps adversarial coverage.
64
+ Synthetic HTTP/browser fixtures use no real files or user data. Actual OpenAI
65
+ product/build/platform access is unavailable here; these tests do not establish
66
+ live OpenAI host compatibility or permit advertising observed host support.
67
+
68
+ Subscription lifecycle regression (#3214 review round 1): notifications during a
69
+ pending write coalesce into a fresh authorized read after the write settles.
70
+ Superseded refreshes retry a read without disposing the session; authority and
71
+ validation failures still terminate it. Disposal dispatches best-effort host
72
+ unsubscribe and releases local registration synchronously, allowing immediate
73
+ replacement even when the host never replies. Deterministic session tests cover
74
+ delayed read/write completion, the refreshed ETag, revocation, and replacement
75
+ while unsubscribe is withheld.
76
+
77
+ Resource URIs are preserved verbatim and reject Windows drive prefixes as well as URLs and filesystem paths. Matching resource-update notifications invalidate in-flight reads before callbacks publish content; refresh retries still obtain fresh authority. A session invalidated by the bridge's initial synchronous snapshot immediately releases its lifecycle listener.
@@ -0,0 +1,42 @@
1
+ # M6d form and resource-selection validation contract
2
+
3
+ Refs #3216. Risk: high — asynchronous authorization and approval integrity.
4
+ Source of wire truth: OpenAI mcp-extensions e314720a0daac326217d1f123fcf51647868fa9f,
5
+ full server/forms/{schema,fields,file-picker,elicitation}.ts. No generated JSON schema artifact.
6
+
7
+ | Behavior / reachable trigger | Positive | Negative / failure | Actor/context | Executor/transaction | Runtime/dialect | External edge | Test level / command |
8
+ | --- | --- | --- | --- | --- | --- | --- | --- |
9
+ | Form declaration before suspension | Primitive, select, array, resource variants | Unknown/oversized/accessor/prototype/schema injection, invalid defaults | Server-owned declaration | N/A: pure validation | Node/browser | Pinned complete schema unions, explicitly narrowed safety profile | Unit / pnpm --filter @happyvertical/smrt-mcp-openai test |
10
+ | Form reply before use | Exact accept content, cancel, decline | Missing/unknown fields, wrong type/options/resource, oversized content | Untrusted host/app reply | N/A: validation precedes authoritative handler | Node/browser | Exact reply union | Unit / same |
11
+ | Durable form continuation | Owner+tenant+record/revision/input key survive restart | Wrong owner/tenant/request/revision, duplicate/concurrent/cancelled reply | Verified application actor, active tenant | Existing McpTaskStore job row and CAS; no new store | SQLite/PostgreSQL | Existing M5 continuation | Integration / same with SMRT_TEST_POSTGRES_URL for PostgreSQL |
12
+ | Live authority and application apply | Fresh authorization at start/resume/apply | Revocation before/after reply, callback failure, unknown external outcome | Verified actor × ownership × tenant | Existing TaskRunner, maxAttempts=1, owning handler transaction | SQLite/PostgreSQL | M5 authority callback; form answer never approval | Integration / same |
13
+ | Capability/native transport decision | Explicit usable application form fallback | Absent/unknown capability, unsupported SDK path, upstream failure | Synthetic SDK client | Same authoritative workflow | SDK v2 HTTP | openai/elicitation create/MRTR support checked explicitly | Integration / same |
14
+ | Functional form presentation | Keyboard usable accept/cancel/decline, escaped options | Inert injected text, invalid values | Synthetic browser user | Submit to owning application workflow; no browser authority | Chromium | Application form fallback, no host certification | Browser / pnpm --filter @happyvertical/smrt-mcp-openai test:forms:e2e |
15
+ | Package boundaries | Packed forms browser and server entries load | SDK v1/server imports in browser | Package consumer | N/A: static bundle/load | Node/browser | Published explicit subpaths | Pack / pnpm --filter @happyvertical/smrt-mcp-openai verify:forms:pack |
16
+
17
+ Full root gates and final independent review are coordinator-owned after #3205 repair.
18
+ Actual OpenAI host verification remains M8; synthetic tests cannot satisfy it.
19
+
20
+ Accepted review regression: for both multi-select and free-text arrays, untouched
21
+ optional fields without defaults are omitted; editing then clearing submits an
22
+ explicit empty array, accepted at minItems=0 and rejected at minItems=1. Untouched
23
+ required arrays still undergo minItems validation. Browser fixtures cover each
24
+ case; the two optional-omission cases fail against the pre-fix renderer.
25
+
26
+ ## PR3283 accepted round-3 corrections
27
+
28
+ | Invariant | Reachable trigger | Positive | Negative/failure | Actor/context and executor | Runtime/contract edge | Level and command |
29
+ |---|---|---|---|---|---|---|
30
+ | Inert shared JSON | form schema/reply and shared native/context/message/mention/settings/file parsers | ordinary nested JSON; validation returns void | array getter/iterator/custom prototype, hidden getter/toJSON, inherited toJSON: zero callbacks; sparse/custom/symbol fields rejected | untrusted caller; no domain executor | Node and browser; pinned bounded JSON schema, unknown non-JSON rejected | full `test`, installed forms/shared-boundary probes |
31
+ | Reversible string arrays | free array form editor defaults and edited values | newline item, empty item, >4096 total with bounded items | invalid JSON, non-string/oversized item, minItems empty | local form input; submission spy/HTTP fixture only, no domain approval | Chromium; existing 64-item/4096-item/65536-byte profile | `test:forms:e2e`, installed validator |
32
+ | Optional empty choice | enum/oneOf selection/default | defined empty choice survives | untouched optional text omitted | local form; authenticated route still owns execution | Chromium select placeholder vs explicit empty option | `test:forms:e2e` |
33
+ | Real date-time | reply/default string validation | leap day, fractional second, offsets crossing date boundary | February31/nonleapFebruary29/April31, hour24, minute/second60, offset overflow | untrusted input; no persistence mutation | Node/public installed API; existing RFC3339-style profile | `test`, installed forms probe |
34
+
35
+ Baseline production48bc with test-only overlays: descriptor/date unit regressions
36
+ and actual Chromium round-trip/choice regressions fail before fixes; overlay hashes
37
+ and exact logs are retained externally in round-3-pr-feedback. Shared `json()`
38
+ callers were audited in validation, index, client, settings, context, messages,
39
+ mentions, file-contracts, files and forms-server: all call the boundary before
40
+ interpreting wire values; stricter descriptor rejection has full owning-package
41
+ unit/browser/installed coverage. SQL/PG/root-wide replay is N/A for these parser
42
+ and presentation-only corrections; existing durable authority remains unchanged.
package/FORMS.md ADDED
@@ -0,0 +1,112 @@
1
+ # Optional extended forms and resource selection
2
+
3
+ `@happyvertical/smrt-mcp-openai/forms` validates the bounded OpenAI wire profile.
4
+ `/forms/view` renders a functional application form without host capabilities.
5
+ `/forms/server` composes that input with the existing durable MCP workflow.
6
+ No form answer supplies identity, file permission, or domain approval.
7
+
8
+ ## Pinned contract and native limitation
9
+
10
+ Wire source: OpenAI mcp-extensions
11
+ [e314720a0daac326217d1f123fcf51647868fa9f](https://github.com/openai/mcp-extensions/tree/e314720a0daac326217d1f123fcf51647868fa9f/typescript/src/server/forms):
12
+ full `fields.ts`, `file-picker.ts`, `schema.ts`, and `elicitation.ts` were inspected.
13
+ The repository supplies TypeScript/Zod source, not a generated JSON schema artifact.
14
+ Official [MCP guidance](https://developers.openai.com/plugins/build/mcp-server)
15
+ requires server-side authorization and says elicitation collects information,
16
+ not credentials or authentication bypasses.
17
+
18
+ The pinned extension advertises `extensions['openai/elicitation'].form` and calls
19
+ `openai/elicitation/create`. SMRT's scoped `@modelcontextprotocol/server` 2.0.0,
20
+ protocol 2026-07-28, has a public `InputRequest` union of `CreateMessageRequest |
21
+ ListRootsRequest | ElicitRequest`. `inputRequired.elicit` emits only
22
+ `elicitation/create`; `ServerContext.mcpReq.elicitInput` explicitly throws on
23
+ 2026-07-28. The public MRTR union does not include the OpenAI method. The upstream
24
+ helper uses monolithic SDK v1 and is not an acceptable substitute. Therefore
25
+ `openAiFormSupport` returns `native:false` even with a present host capability,
26
+ with `sdk-mrtr-unsupported`. Absent and unknown capabilities also select the
27
+ functional application-form fallback. No OpenAI extension capability is
28
+ advertised, no callback is kept alive, and the stateless mount is unchanged.
29
+ Actual OpenAI host support is unverified and remains the M8 acceptance gate.
30
+
31
+ ## Bounded wire profile
32
+
33
+ Supported fields cover boolean, number/integer, string, titled/legacy single
34
+ select, titled/untitled multi-select, string arrays, suggestions, thumbnail/preview
35
+ metadata, and file/resource picker strings or arrays. Replies are exactly
36
+ `{action:'accept',content:{...}}`, `{action:'cancel'}`, or `{action:'decline'}`.
37
+ The parser rejects unknown fields instead of stripping semantic instructions.
38
+ Limits: 64 fields/options/selections, 4096-character values, 256-character resource
39
+ names, 2048-character URIs, 65536-byte inert JSON budget, depth 16. Numeric bounds
40
+ must be finite; integer answers must be safe integers. Required/enum/default and
41
+ resource-selection rules are checked without coercion or inserted defaults.
42
+
43
+ Deliberate stricter safety profile: arbitrary regex `pattern` is rejected rather
44
+ than evaluated; prototype-sensitive names and accessors are rejected; images
45
+ must be HTTPS without credentials or bounded raster data URIs. The fallback
46
+ renderer does not fetch images. Resources must use opaque absolute URIs;
47
+ `file:`, `data:`, and `javascript:` are rejected, and a remote application cannot
48
+ interpret a desktop path. The owning grant provider must authorize every selected
49
+ URI, including offered options and implicit/user-supplied selections. Selection
50
+ is input only. Unknown resource metadata does not become permission.
51
+
52
+ ## Durable application integration
53
+
54
+ Inside the existing task runner, call `continueOpenAiForm` with a server-owned
55
+ `McpWorkflowContinuation` (`recordId`, immutable `revision`, unique `inputKey`,
56
+ HTTPS or loopback `reviewUrl`), schema, fresh `authorizeApply`, and `applyInput`.
57
+ Forms containing pickers also require `authorizeResource`. The task must already
58
+ have the exact same continuation binding. M5 persists its owner and active tenant,
59
+ checks live task authority at start/resume, and uses one job row for suspension,
60
+ first-response CAS, cancellation, and execution. No new approval store exists.
61
+
62
+ The authenticated application form reads the descriptor through
63
+ `createMcpContinuationTool` or its actor/tenant-bound `McpTaskStore`, renders it
64
+ with `renderOpenAiForm`, then calls `submitOpenAiForm` on its server. Supply that
65
+ verified actor's tenant-bound store and a policy callback which resolves fresh
66
+ application authority. Never derive either from submitted form values. Submit
67
+ validates the stored schema, a canonical SHA-256 schema fingerprint, and exact binding before persisting a bound reply;
68
+ the worker validates again, checks selected resource grants, then reauthorizes both application access and active task ownership after asynchronous checks
69
+ before `applyInput`. Direct `tasks/update` cannot bypass worker validation.
70
+
71
+ A submitted reply is not a completed operation. Concurrent replies use the
72
+ existing store's first-response-wins CAS; stale/cancelled/consumed requests fail
73
+ closed. Poll the task for its authoritative result. Worker restart replays the
74
+ handler, so all code before suspension must be read-only or use the existing
75
+ owning action/runOnce idempotency API. Remote jobs use `maxAttempts:1`; unknown
76
+ external outcomes fail for manual reconciliation, not automatic replay.
77
+ `applyInput` must enforce domain review and immutable revision in its owning
78
+ transaction, and external access must recheck applicable authority at its own
79
+ boundary. Iolaus retains its dedicated human review page and no employer
80
+ transmission occurs in fixtures. Accepting a form does not approve an application.
81
+
82
+ Run package build, typecheck, test, test:forms:e2e and verify:forms:pack. See
83
+ [FORMS-TEST-DESIGN.md](FORMS-TEST-DESIGN.md) for the evidence contract. Tests use
84
+ synthetic data only; PostgreSQL authority tests use `SMRT_TEST_POSTGRES_URL`.
85
+
86
+ In the application form, an untouched optional array without a default is
87
+ omitted from the reply. Editing then clearing an array submits an explicit
88
+ empty array, which must satisfy `minItems`. Required arrays and declared
89
+ defaults always remain subject to the schema's normal reply validation.
90
+
91
+ ### Inert inputs and reversible fallback values
92
+
93
+ Form schemas and replies are interpreted and serialized from a detached snapshot
94
+ of own enumerable data descriptors. Accessors, sparse/custom-prototype arrays,
95
+ custom array properties/iterators, hidden fields and serialization hooks are
96
+ rejected without invoking supplied callbacks. The shared JSON validator retains
97
+ its validation-only return contract and existing depth/node/byte bounds; ordinary
98
+ JSON data remains supported. Native, context, messages, mentions, settings, file
99
+ and continuation callers share this boundary. Host callbacks/functions belong in
100
+ adapter options, never in wire JSON.
101
+
102
+ The free string-array fallback uses an explicitly labeled JSON array editor:
103
+ `["first", "second"]`, `["a\nb"]`, `[""]` and `[]` remain distinct. Each item and
104
+ the complete reply retain their existing limits; the editor allows up to 65,536
105
+ characters and wire validation enforces the byte limit. Invalid JSON remains
106
+ editable and does not submit. Untouched optional arrays/text remain omitted;
107
+ intentional empty choices and empty defaults are retained. A blank array editor
108
+ means an intentional empty list after interaction, preserving minItems checks.
109
+
110
+ Date-time replies require a real calendar date, hours 00–23, minutes/seconds
111
+ 00–59 and offset hours/minutes 00–23/00–59, in the existing `T` and `Z`/offset
112
+ profile. Invalid dates normalized by JavaScript's Date parser are rejected.
@@ -0,0 +1,34 @@
1
+ export interface OpenAiFileInput {
2
+ file: {
3
+ name: string;
4
+ resourceUri: string;
5
+ };
6
+ }
7
+ export type FileContent = {
8
+ text: string;
9
+ blob?: never;
10
+ } | {
11
+ blob: string;
12
+ text?: never;
13
+ };
14
+ export type FileWriteResult = {
15
+ outcome: 'saved' | 'conflict';
16
+ etag: string;
17
+ } | {
18
+ outcome: 'too-large';
19
+ maxBytes: number;
20
+ };
21
+ export interface FileRead {
22
+ uri: string;
23
+ mimeType: string;
24
+ content: FileContent;
25
+ writable: boolean;
26
+ etag?: string;
27
+ }
28
+ /** The URI is retained verbatim and is never decoded into a filesystem path. */
29
+ export declare function validateFileInput(value: unknown): OpenAiFileInput;
30
+ export declare function fileExtensions(value: readonly string[]): string[];
31
+ export declare function fileContent(value: unknown, maxBytes: number): FileContent;
32
+ export declare function fileRead(value: unknown, uri: string, maxBytes: number, mimeTypes: readonly string[]): FileRead;
33
+ export declare function fileWriteResult(value: unknown): FileWriteResult;
34
+ //# sourceMappingURL=file-contracts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-contracts.d.ts","sourceRoot":"","sources":["../src/file-contracts.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AACD,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,KAAK,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AACnC,MAAM,MAAM,eAAe,GACvB;IAAE,OAAO,EAAE,OAAO,GAAG,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAC/C,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AACD,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CA0BjE;AACD,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAiBjE;AACD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAwBzE;AACD,wBAAgB,QAAQ,CACtB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,QAAQ,CA8BV;AACD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAY/D"}
@@ -0,0 +1,98 @@
1
+ import { json, keys, record, text } from './validation.js';
2
+ /** The URI is retained verbatim and is never decoded into a filesystem path. */
3
+ export function validateFileInput(value) {
4
+ json(value);
5
+ const root = record(value);
6
+ keys(root, ['file']);
7
+ const file = record(root.file);
8
+ keys(file, ['name', 'resourceUri']);
9
+ const name = text(file.name, 255);
10
+ if (/[\\/]/.test(name) ||
11
+ [...name].some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127) ||
12
+ name === '.' ||
13
+ name === '..')
14
+ throw new TypeError('Expected filename without path');
15
+ const uri = text(file.resourceUri, 2048);
16
+ // Conservative local policy: opaque host/provider URI, never a URL or file path.
17
+ if (!/^[a-z][a-z0-9+.-]*:/i.test(uri) ||
18
+ /^(file|https?|data|javascript):/i.test(uri) ||
19
+ /^[a-z]:/i.test(uri) ||
20
+ /[\s\\]/.test(uri))
21
+ throw new TypeError('Expected opaque resource URI');
22
+ return { file: { name, resourceUri: uri } };
23
+ }
24
+ export function fileExtensions(value) {
25
+ if (!Array.isArray(value) ||
26
+ !value.length ||
27
+ value.length > 32 ||
28
+ new Set(value.map((extension) => typeof extension === 'string' ? extension.toLowerCase() : extension)).size !== value.length)
29
+ throw new TypeError('Expected bounded unique file extensions');
30
+ return value.map((extension) => {
31
+ if (!/^\.[a-z0-9][a-z0-9._-]{0,31}$/i.test(extension))
32
+ throw new TypeError('Expected file extension');
33
+ return extension.toLowerCase();
34
+ });
35
+ }
36
+ export function fileContent(value, maxBytes) {
37
+ json(value);
38
+ const content = record(value);
39
+ keys(content, ['text', 'blob']);
40
+ let bytes;
41
+ if (typeof content.text === 'string' && !Object.hasOwn(content, 'blob')) {
42
+ bytes = new TextEncoder().encode(content.text).length;
43
+ }
44
+ else if (typeof content.blob === 'string' &&
45
+ !Object.hasOwn(content, 'text')) {
46
+ const blob = content.blob;
47
+ if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(blob))
48
+ throw new TypeError('Expected base64 content');
49
+ bytes =
50
+ (blob.length / 4) * 3 -
51
+ (blob.endsWith('==') ? 2 : blob.endsWith('=') ? 1 : 0);
52
+ }
53
+ else
54
+ throw new TypeError('Expected exactly one representation');
55
+ if (bytes > maxBytes)
56
+ throw new TypeError('File exceeds byte limit');
57
+ return structuredClone(content);
58
+ }
59
+ export function fileRead(value, uri, maxBytes, mimeTypes) {
60
+ const root = record(value);
61
+ if (!Array.isArray(root.contents) || root.contents.length !== 1)
62
+ throw new TypeError('Expected one granted resource');
63
+ const item = record(root.contents[0]);
64
+ if (item.uri !== uri)
65
+ throw new TypeError('Resource URI mismatch');
66
+ const mimeType = text(item.mimeType, 128);
67
+ if (!mimeTypes.includes(mimeType))
68
+ throw new TypeError('File MIME denied');
69
+ const content = fileContent({
70
+ ...(Object.hasOwn(item, 'text') ? { text: item.text } : {}),
71
+ ...(Object.hasOwn(item, 'blob') ? { blob: item.blob } : {}),
72
+ }, maxBytes);
73
+ const metadata = item._meta === undefined
74
+ ? {}
75
+ : record(record(item._meta)['openai/resource'] ?? {});
76
+ if (metadata.writable !== undefined && typeof metadata.writable !== 'boolean')
77
+ throw new TypeError('Invalid writable metadata');
78
+ const etag = metadata.etag === undefined ? undefined : text(metadata.etag, 1024);
79
+ return {
80
+ uri,
81
+ mimeType,
82
+ content,
83
+ writable: metadata.writable === true,
84
+ ...(etag ? { etag } : {}),
85
+ };
86
+ }
87
+ export function fileWriteResult(value) {
88
+ json(value);
89
+ const result = record(value);
90
+ if (result.outcome === 'saved' || result.outcome === 'conflict')
91
+ return { outcome: result.outcome, etag: text(result.etag, 1024) };
92
+ if (result.outcome === 'too-large' &&
93
+ Number.isSafeInteger(result.maxBytes) &&
94
+ Number(result.maxBytes) >= 0)
95
+ return { outcome: 'too-large', maxBytes: Number(result.maxBytes) };
96
+ throw new TypeError('Invalid file write result');
97
+ }
98
+ //# sourceMappingURL=file-contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-contracts.js","sourceRoot":"","sources":["../src/file-contracts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type McpWorkflowToolDefinition } from '@happyvertical/smrt-app-mcp';
2
+ export type { OpenAiFileInput } from './file-contracts.js';
3
+ export { validateFileInput } from './file-contracts.js';
4
+ /** Wrap an existing authorized provider/view workflow. Input is a reference, never a grant. */
5
+ export declare function withOpenAiFileEntrypoint(definition: McpWorkflowToolDefinition, extensions: readonly string[]): McpWorkflowToolDefinition;
6
+ //# sourceMappingURL=files-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files-server.d.ts","sourceRoot":"","sources":["../src/files-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,6BAA6B,CAAC;AAGrC,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,+FAA+F;AAC/F,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,yBAAyB,EACrC,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,yBAAyB,CA6C3B"}
@@ -0,0 +1,45 @@
1
+ import { createMcpWorkflowTool, } from '@happyvertical/smrt-app-mcp';
2
+ import { fileExtensions, validateFileInput } from './file-contracts.js';
3
+ export { validateFileInput } from './file-contracts.js';
4
+ /** Wrap an existing authorized provider/view workflow. Input is a reference, never a grant. */
5
+ export function withOpenAiFileEntrypoint(definition, extensions) {
6
+ createMcpWorkflowTool(definition);
7
+ const allowed = fileExtensions(extensions);
8
+ if (!definition.ui?.resourceUri ||
9
+ definition.effect !== 'read' ||
10
+ !definition.idempotent)
11
+ throw new TypeError('File entrypoint requires idempotent read UI workflow');
12
+ if (definition.metadata && Object.hasOwn(definition.metadata, 'openai/ui'))
13
+ throw new TypeError('File entrypoint metadata already declared');
14
+ return {
15
+ ...definition,
16
+ inputSchema: {
17
+ type: 'object',
18
+ properties: {
19
+ file: {
20
+ type: 'object',
21
+ properties: {
22
+ name: { type: 'string' },
23
+ resourceUri: { type: 'string' },
24
+ },
25
+ required: ['name', 'resourceUri'],
26
+ additionalProperties: false,
27
+ },
28
+ },
29
+ required: ['file'],
30
+ additionalProperties: false,
31
+ },
32
+ metadata: {
33
+ ...structuredClone(definition.metadata ?? {}),
34
+ 'openai/ui': { entrypoints: [{ type: 'file', extensions: allowed }] },
35
+ },
36
+ execute(context) {
37
+ const input = validateFileInput(context.arguments);
38
+ if (!allowed.some((extension) => input.file.name.toLowerCase().endsWith(extension)))
39
+ throw new TypeError('File extension denied');
40
+ // Existing handler must resolve ownership/tenant from context.principal, never metadata.
41
+ return definition.execute({ ...context, arguments: { ...input } });
42
+ },
43
+ };
44
+ }
45
+ //# sourceMappingURL=files-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files-server.js","sourceRoot":"","sources":["../src/files-server.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GAEtB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,+FAA+F;AAC/F,MAAM,UAAU,wBAAwB,CACtC,UAAqC,EACrC,UAA6B;IAE7B,qBAAqB,CAAC,UAAU,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC3C,IACE,CAAC,UAAU,CAAC,EAAE,EAAE,WAAW;QAC3B,UAAU,CAAC,MAAM,KAAK,MAAM;QAC5B,CAAC,UAAU,CAAC,UAAU;QAEtB,MAAM,IAAI,SAAS,CAAC,sDAAsD,CAAC,CAAC;IAC9E,IAAI,UAAU,CAAC,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC;QACxE,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAC;IACnE,OAAO;QACL,GAAG,UAAU;QACb,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,UAAU,EAAE;wBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;wBACxB,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;qBAChC;oBACD,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,CAAC;oBACjC,oBAAoB,EAAE,KAAK;iBAC5B;aACF;YACD,QAAQ,EAAE,CAAC,MAAM,CAAC;YAClB,oBAAoB,EAAE,KAAK;SAC5B;QACD,QAAQ,EAAE;YACR,GAAG,eAAe,CAAC,UAAU,CAAC,QAAQ,IAAI,EAAE,CAAC;YAC7C,WAAW,EAAE,EAAE,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE;SACtE;QACD,OAAO,CAAC,OAAO;YACb,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACnD,IACE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAC1B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAClD;gBAED,MAAM,IAAI,SAAS,CAAC,uBAAuB,CAAC,CAAC;YAC/C,yFAAyF;YACzF,OAAO,UAAU,CAAC,OAAO,CAAC,EAAE,GAAG,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,KAAK,EAAE,EAAE,CAAC,CAAC;QACrE,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { McpAppBridge, ToolResult } from '@happyvertical/smrt-mcp-apps';
2
+ import { type FileContent, type FileRead, type FileWriteResult, type OpenAiFileInput } from './file-contracts.js';
3
+ export type { FileContent, FileRead, FileWriteResult, OpenAiFileInput, } from './file-contracts.js';
4
+ export { validateFileInput } from './file-contracts.js';
5
+ export type FileOperation = 'read' | 'write' | 'subscribe' | 'open';
6
+ export interface OpenAiFileOptions {
7
+ bridge: McpAppBridge;
8
+ input: OpenAiFileInput;
9
+ /** Existing app workflow: fresh principal/owner/tenant/grant check on every call. */
10
+ authorityTool: string;
11
+ /** Ordinary import/open/download workflow, used when native capability is absent. */
12
+ fallbackTool: string;
13
+ /** Application policy, capped below the portable bridge's JSON envelope limit. */
14
+ maxBytes: number;
15
+ mimeTypes: readonly string[];
16
+ }
17
+ /** One initial tool-input grant, one lifetime; create a new session after reconnect/input change. */
18
+ export declare class OpenAiFileSession {
19
+ #private;
20
+ constructor(options: OpenAiFileOptions);
21
+ get native(): boolean;
22
+ get signal(): AbortSignal;
23
+ fallback(operation: FileOperation): Promise<ToolResult>;
24
+ read(representation?: 'text' | 'blob'): Promise<FileRead | ToolResult>;
25
+ write(content: FileContent): Promise<FileWriteResult | ToolResult>;
26
+ subscribe(onRead: (file: FileRead) => void, onError: (error: unknown) => void): Promise<void>;
27
+ dispose(): void;
28
+ }
29
+ /** User action calls this after the existing provider selects and authorizes an exact path. */
30
+ export declare function openOpenAiFile(options: {
31
+ bridge: McpAppBridge;
32
+ resolveTool: string;
33
+ arguments: Record<string, unknown>;
34
+ allowedPaths: readonly string[];
35
+ /** Explicit application confirmation UI; false cancels without resolving or opening. */
36
+ confirm(): Promise<boolean>;
37
+ }): Promise<ToolResult | undefined>;
38
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,eAAe,EAIpB,KAAK,eAAe,EAErB,MAAM,qBAAqB,CAAC;AAG7B,YAAY,EACV,WAAW,EACX,QAAQ,EACR,eAAe,EACf,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAKxD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;AACpE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,eAAe,CAAC;IACvB,qFAAqF;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,YAAY,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAeD,qGAAqG;AACrG,qBAAa,iBAAiB;;gBAYhB,OAAO,EAAE,iBAAiB;IAkDtC,IAAI,MAAM,IAAI,OAAO,CAEpB;IACD,IAAI,MAAM,IAAI,WAAW,CAExB;IAkBK,QAAQ,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC;IAWvD,IAAI,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC;IAiCtE,KAAK,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,GAAG,UAAU,CAAC;IAkClE,SAAS,CACb,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,EAChC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC;IAwEhB,OAAO,IAAI,IAAI;CAsBhB;AAED,+FAA+F;AAC/F,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,wFAAwF;IACxF,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7B,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA6BlC"}