@happyvertical/smrt-mcp-openai 0.51.38 → 0.51.39
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 +77 -0
- package/FORMS-TEST-DESIGN.md +42 -0
- package/FORMS.md +112 -0
- package/dist/file-contracts.d.ts +34 -0
- package/dist/file-contracts.d.ts.map +1 -0
- package/dist/file-contracts.js +98 -0
- package/dist/file-contracts.js.map +1 -0
- package/dist/files-server.d.ts +6 -0
- package/dist/files-server.d.ts.map +1 -0
- package/dist/files-server.js +45 -0
- package/dist/files-server.js.map +1 -0
- package/dist/files.d.ts +38 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +288 -0
- package/dist/files.js.map +1 -0
- package/dist/forms-server.d.ts +53 -0
- package/dist/forms-server.d.ts.map +1 -0
- package/dist/forms-server.js +115 -0
- package/dist/forms-server.js.map +1 -0
- package/dist/forms-view.d.ts +14 -0
- package/dist/forms-view.d.ts.map +1 -0
- package/dist/forms-view.js +213 -0
- package/dist/forms-view.js.map +1 -0
- package/dist/forms.d.ts +118 -0
- package/dist/forms.d.ts.map +1 -0
- package/dist/forms.js +389 -0
- package/dist/forms.js.map +1 -0
- package/dist/mentions.d.ts.map +1 -1
- package/dist/mentions.js +1 -0
- package/dist/mentions.js.map +1 -1
- package/dist/validation.d.ts +3 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +47 -21
- package/dist/validation.js.map +1 -1
- package/package.json +38 -7
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"}
|
package/dist/files.d.ts
ADDED
|
@@ -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"}
|