@happyvertical/smrt-mcp-openai 0.0.0-stage → 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.
Files changed (60) hide show
  1. package/AGENTS.md +11 -0
  2. package/CLAUDE.md +1 -0
  3. package/FILES.md +77 -0
  4. package/FORMS-TEST-DESIGN.md +42 -0
  5. package/FORMS.md +112 -0
  6. package/LICENSE +7 -0
  7. package/README.md +175 -2
  8. package/dist/client.d.ts +27 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +125 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/context.d.ts +47 -0
  13. package/dist/context.d.ts.map +1 -0
  14. package/dist/context.js +110 -0
  15. package/dist/context.js.map +1 -0
  16. package/dist/file-contracts.d.ts +34 -0
  17. package/dist/file-contracts.d.ts.map +1 -0
  18. package/dist/file-contracts.js +98 -0
  19. package/dist/file-contracts.js.map +1 -0
  20. package/dist/files-server.d.ts +6 -0
  21. package/dist/files-server.d.ts.map +1 -0
  22. package/dist/files-server.js +45 -0
  23. package/dist/files-server.js.map +1 -0
  24. package/dist/files.d.ts +38 -0
  25. package/dist/files.d.ts.map +1 -0
  26. package/dist/files.js +288 -0
  27. package/dist/files.js.map +1 -0
  28. package/dist/forms-server.d.ts +53 -0
  29. package/dist/forms-server.d.ts.map +1 -0
  30. package/dist/forms-server.js +115 -0
  31. package/dist/forms-server.js.map +1 -0
  32. package/dist/forms-view.d.ts +14 -0
  33. package/dist/forms-view.d.ts.map +1 -0
  34. package/dist/forms-view.js +213 -0
  35. package/dist/forms-view.js.map +1 -0
  36. package/dist/forms.d.ts +118 -0
  37. package/dist/forms.d.ts.map +1 -0
  38. package/dist/forms.js +389 -0
  39. package/dist/forms.js.map +1 -0
  40. package/dist/index.d.ts +47 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +205 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/mentions.d.ts +30 -0
  45. package/dist/mentions.d.ts.map +1 -0
  46. package/dist/mentions.js +149 -0
  47. package/dist/mentions.js.map +1 -0
  48. package/dist/messages.d.ts +19 -0
  49. package/dist/messages.d.ts.map +1 -0
  50. package/dist/messages.js +47 -0
  51. package/dist/messages.js.map +1 -0
  52. package/dist/settings.d.ts +45 -0
  53. package/dist/settings.d.ts.map +1 -0
  54. package/dist/settings.js +158 -0
  55. package/dist/settings.js.map +1 -0
  56. package/dist/validation.d.ts +15 -0
  57. package/dist/validation.d.ts.map +1 -0
  58. package/dist/validation.js +121 -0
  59. package/dist/validation.js.map +1 -0
  60. package/package.json +96 -3
package/AGENTS.md ADDED
@@ -0,0 +1,11 @@
1
+ # Optional OpenAI navigation adapter
2
+
3
+ `src/index.ts` composes existing principal-authorized app-MCP workflows. Settings
4
+ persistence, atomic assignment and tenant checks stay in the owning application
5
+ handler. The adapter validates wire contracts; it never authenticates or retries
6
+ writes. `src/client.ts` uses only the portable browser bridge and bounded inert
7
+ host context. No SDK v1 helper, second server, or OpenAI types in portable APIs.
8
+
9
+ Pinned contract, fallbacks, package gates and external-host evidence limitations
10
+ are in README.md; behavior/threat rows are in TEST-DESIGN.md. Run build, typecheck,
11
+ test, test:e2e and verify:pack. Synthetic hosts do not prove OpenAI host support.
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
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.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Happy Vertical Corporation>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,176 @@
1
- # Temporary Holding Version
1
+ # @happyvertical/smrt-mcp-openai
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Optional navigation, settings, context, messages and mentions for s-m-r-t MCP Apps. Ordinary s-m-r-t apps do not
4
+ import this package. The `./client` entry is browser-only and depends on the
5
+ portable `@happyvertical/smrt-mcp-apps` bridge; the root entry composes existing
6
+ app-MCP workflows without creating another server or authorization policy.
7
+
8
+ ## Contract and scope
9
+
10
+ This package implements the navigation/settings and context/messages/mentions children of [#3202](https://github.com/happyvertical/smrt/issues/3202)
11
+ under the [M0 integration standard](../../docs/content/architecture/mcp-apps-integration.md):
12
+
13
+ - Global and thread entrypoint metadata on idempotent read UI tools accepting `{}`.
14
+ - Structured primitive settings and permission-filtered layout actions; modern
15
+ discovery advertises `capabilities.extensions['openai/settings']` only when
16
+ both settings tools are in the request's authorized catalog.
17
+ - Resource display-mode metadata and portable display requests with inline fallback.
18
+ - Validated native/web app links and observed host deep links resolved through an
19
+ existing domain-authorized read tool.
20
+ - Manifest onboarding declaration; packaged skill/assets verification belongs to M7.
21
+
22
+ Exact baseline: [OpenAI extension spec e314720](https://github.com/openai/mcp-extensions/blob/e314720a0daac326217d1f123fcf51647868fa9f/docs/spec.md),
23
+ revalidated as upstream main on 2026-09-30 UTC, and the [official extension guide](https://developers.openai.com/plugins/build/extensions)
24
+ and [UI guide](https://developers.openai.com/plugins/build/chatgpt-ui). The SDK v1
25
+ OpenAI helper is deliberately not a dependency. Server transport uses native
26
+ scoped MCP SDK 2.0.0 / protocol `2026-07-28`; browser Apps uses `2026-01-26`.
27
+
28
+ Only implemented features are declared. Files/forms remain separate planned M6 children. Settings support booleans, strings/enums/length bounds,
29
+ numbers/integers/range/multiple bounds. Arbitrary regex patterns are explicitly
30
+ rejected to avoid running unbounded patterns; applications can use an enum or
31
+ an existing domain validator. No silent schema downgrade occurs. Metadata is
32
+ bounded to 64 KiB, depth 16; settings to 64 fields, strings to 4096 characters,
33
+ routes to 2048 characters. Unknown fields/types fail closed.
34
+
35
+ ## Server composition
36
+
37
+ Use `withOpenAiEntrypoints(existingReadWorkflow, ['global', 'thread'])` and pass
38
+ the result to the existing `createMcpAppServer({ workflowTools })`. It retains the
39
+ handler and portable `_meta.ui.resourceUri`. Initial tool results still render
40
+ without duplicate entrypoint calls. Give thread views distinct descriptive titles.
41
+
42
+ `bindOpenAiSettings({ schema, read, update, server: () => appServer })` wraps two
43
+ existing workflow definitions. Add its `workflows` to that server, then mount it
44
+ with `extensions: ({ tools }) => settings.extensions(tools)`. The callback runs
45
+ on `server/discover` with the same verified principal and filtered catalog used
46
+ for tool discovery. Neither annotations nor extension metadata grant permission.
47
+
48
+ Read handlers return `structuredContent: { schema, values, layout? }`, with a
49
+ value for every property. Update handlers receive `{ set: { changedField: value } }`
50
+ and return `structuredContent: { values }`. Updates are absolute-value assignments,
51
+ not toggles or increments. The binding verifies their declared read/write effects,
52
+ requires idempotent assignment, validates inputs/outputs, and never retries.
53
+
54
+ **Owning handlers must authorize resource ownership, recheck active tenant and
55
+ membership, and persist patches atomically using their existing executor and
56
+ revision policy.** The adapter does not provide persistence, transaction guards,
57
+ or domain approval. The host wire contract has no revision parameter; do not add
58
+ an invented field. Workflows requiring a human review/revision token should keep
59
+ that flow in the application, rather than force it into native settings. Tests
60
+ exercise an owning SQLite transaction, guarded update, rollback and retries.
61
+
62
+ Pass `openAiDisplayMetadata({ availableDisplayModes: ['inline', 'fullscreen'],
63
+ preferredDisplayMode: 'fullscreen' })` as a declared resource's `metadata`.
64
+ Resource security metadata and digest remain owned by app-MCP. `pip` is not part
65
+ of this pinned OpenAI display contract. Omitted fields preserve upstream defaults;
66
+ preferences remain hints.
67
+
68
+ `openAiOnboardingDeclaration('./skills/setup/SKILL.md')` returns only
69
+ `{ extensions: { 'com.openai': { onboardingSkill: ... } } }`. Merge that into the
70
+ plugin manifest; it does not install or execute the skill.
71
+
72
+ ## Browser navigation and fallbacks
73
+
74
+ Import `./client`, pass the existing `McpAppBridge` to
75
+ `observeOpenAiNavigation({ bridge, resolveTool, onResult, onFallback })`, and call
76
+ the returned disposer when unmounting. It consumes bounded `rawHostContext` from
77
+ the bridge's validated origin/source and lifecycle, recognizes only
78
+ `openai/deepLink: { url }`, and calls the designated server tool with `{ url }`.
79
+ The domain tool must reject guessed or cross-tenant objects. Server applications
80
+ can use `resolveOpenAiNavigationTarget` to dispatch only an authorized read tool.
81
+
82
+ Repeated identical routes do not duplicate calls; new routes cancel old requests
83
+ and ignore stale replies. URLs are app-relative, fragment-free, traversal-free,
84
+ and reject protocol-relative, double-encoded and control-character forms. Treat
85
+ query parameters as untrusted input, never as identity or approval. The literal
86
+ query delimiter is separated before pathname decoding, so encoded question marks
87
+ cannot hide traversal segments. Traversal-like text in query values remains legal.
88
+
89
+ Only a rejected tool call or an `isError` result selects the `denied` fallback.
90
+ Consumer callback exceptions are not authorization failures and never retry a
91
+ fallback. Callbacks should handle their own rendering errors; uncaught exceptions
92
+ in asynchronous result/fallback callbacks surface as unhandled promise rejections.
93
+
94
+ `createOpenAiAppLink` emits pinned desktop (`codex`), mobile (`chatgpt`) or web
95
+ formats with separately encoded plugin/tool/path components. `openAiNavigationLink`
96
+ requires explicit `'present'` support to choose a native link; `'absent'`, unknown
97
+ or malformed support uses a configured HTTPS application URL. This support input
98
+ is a deployment/observed-host decision, **not an invented MCP capability field**.
99
+ Entrypoints/onboarding similarly have no client advertisement in the pinned spec;
100
+ keep ordinary tool/setup navigation available. Native structured settings are
101
+ optional: the same settings tools and application settings page remain usable.
102
+
103
+ `requestOpenAiDisplayMode` uses negotiated portable modes; missing/unknown modes
104
+ or a rejected request return `'inline'`. The UI must actually retain its inline
105
+ layout and render an ordinary link when navigation cannot be resolved.
106
+
107
+ ## Context, messages and composer mentions
108
+
109
+ Browser consumers import `updateOpenAiModelContext` from `./context` and
110
+ `sendOpenAiMessage` from `./messages`. Server consumers import
111
+ `withOpenAiMentionSearch` and `resolveOpenAiMentionSelection` from `./mentions`.
112
+ These subpaths share the existing portable bridge and authorized app-MCP server.
113
+
114
+ `updateOpenAiModelContext({ bridge, value, native })` replaces context with
115
+ `{ text: { text, title?, thumbnail? }, structuredContent? }`. Text is nonempty and
116
+ at most 16384 characters; titles are limited to 512. Set `background: true` on the text value to emit assistant-only audience
117
+ metadata; background is a presentation
118
+ hint, **not a privacy boundary**: the model/provider still receives the context.
119
+ Only send data authorized for that recipient. Negotiated `openai/modelContext`
120
+ uses the supplied native callback. Missing support or native failure falls back
121
+ to portable text and structured content, without native metadata. Abort/disposal
122
+ prevents a late fallback.
123
+
124
+ `sendOpenAiMessage({ bridge, value, native })` accepts text and `target: 'active'`
125
+ (the default) or `'new'`. Active conversations can use portable text messages.
126
+ New conversations require negotiated `openai/message` and a native callback;
127
+ missing or malformed support rejects before any message reaches the active
128
+ conversation. Unknown targets/modalities fail closed and sends are not retried.
129
+
130
+ `withOpenAiMentionSearch(existingReadWorkflow)` declares app-only composer search
131
+ metadata and a `{ query }` input (at most 256 characters, including empty search).
132
+ Its owning handler returns `structuredContent: { items }`, with at most 25
133
+ `{ type: 'resource', resourceUri, title, subtitle?, icons? }` entries. Handles
134
+ are nonempty opaque strings of at most 2048 characters; titles/subtitles are
135
+ nonempty and bounded to 512. Each of at most eight icons requires `src` (2048),
136
+ with optional `mimeType` (128), up to eight nonempty `sizes` strings (32 each),
137
+ and `theme: 'light' | 'dark'`. Unknown fields and malformed values reject.
138
+ The published output schema describes these same closed objects and limits.
139
+
140
+ Search results are discovery hints, never authorization grants. Pass the exact
141
+ returned `resourceUri` as the existing selection workflow's argument through
142
+ `resolveOpenAiMentionSelection({ server, tool, arguments, principal })`. The
143
+ application supplies the trusted, freshly resolved principal; app-MCP policy
144
+ rechecks actor, active tenant, scopes and membership on every call, and the
145
+ owning workflow rechecks handle ownership. Never derive identity from a handle,
146
+ browser metadata or search-time permission. Guesses, cross-tenant calls and
147
+ revocation after search must not select private data. This helper adds no
148
+ selection handler, identity provider, persistence or authorization policy.
149
+
150
+ ## Validation and evidence
151
+
152
+ Run `pnpm install`, then the documented dependency build and package gates:
153
+
154
+ ```sh
155
+ pnpm exec turbo build --filter=@happyvertical/smrt-mcp-openai...
156
+ pnpm --filter @happyvertical/smrt-mcp-openai typecheck
157
+ pnpm --filter @happyvertical/smrt-mcp-openai test
158
+ pnpm --filter @happyvertical/smrt-mcp-openai test:e2e
159
+ pnpm --filter @happyvertical/smrt-mcp-openai verify:pack
160
+ ```
161
+
162
+ The behavior/threat matrix is in [TEST-DESIGN.md](TEST-DESIGN.md). Protocol tests
163
+ use the actual SDK client and HTTP mount, including real loopback JWKS/MCP sockets
164
+ through the public M2 auth gateway with token, actor, tenant and revocation denials; the browser suite uses a real Chromium
165
+ sandboxed iframe with no-network CSP and built browser output. Packed exports
166
+ are checked for declaration and browser/server boundaries. Existing app-MCP full
167
+ suite/typecheck/pack and portable bridge gates also apply to integration changes.
168
+ The coordinator schedules all root gates and independent review.
169
+
170
+ **No actual OpenAI host compatibility is claimed.** The synthetic host does not
171
+ prove desktop, Work web, iOS, Android or account rollout support. No configured
172
+ OpenAI host connection or deployable registered synthetic plugin was provided;
173
+ record exact host/build/platform, auth profile, asset revision and observed
174
+ navigation/settings outcomes before claiming supported-host acceptance. The
175
+ upstream support matrix is expected support only; web refers to Work, excluding
176
+ classic ChatGPT. Actual host acceptance remains open for M8.
@@ -0,0 +1,27 @@
1
+ import type { McpAppBridge, ToolResult } from '@happyvertical/smrt-mcp-apps';
2
+ import { type OpenAiDisplayMode } from './validation.js';
3
+ export { appRelativeUrl, OPENAI_EXTENSIONS_REVISION } from './validation.js';
4
+ export type CapabilityState = 'present' | 'absent' | 'unknown';
5
+ /** Deployment/host-observation input, not an invented MCP wire capability. */
6
+ export declare function capabilityState(value: unknown): CapabilityState;
7
+ export interface OpenAiAppLink {
8
+ pluginId: string;
9
+ toolName: string;
10
+ path?: string;
11
+ marketplace?: string;
12
+ platform: 'desktop' | 'mobile' | 'web';
13
+ }
14
+ export declare function createOpenAiAppLink(options: OpenAiAppLink): string;
15
+ /** No host-brand inference: absent/unverified support yields an ordinary application link. */
16
+ export declare function openAiNavigationLink(capability: unknown, link: OpenAiAppLink, fallbackUrl: string): string;
17
+ /** Display negotiation remains portable; unknown modes and host failures retain inline UI. */
18
+ export declare function requestOpenAiDisplayMode(bridge: McpAppBridge, requested: unknown): Promise<OpenAiDisplayMode>;
19
+ export declare function readOpenAiDeepLink(rawHostContext: unknown): string | undefined;
20
+ /** Observe inert host context, then request a domain-authorized read; never route to arbitrary URLs. */
21
+ export declare function observeOpenAiNavigation(options: {
22
+ bridge: McpAppBridge;
23
+ resolveTool: string;
24
+ onResult(result: ToolResult, url: string): void;
25
+ onFallback(reason: 'unavailable' | 'invalid' | 'denied'): void;
26
+ }): () => void;
27
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAKL,KAAK,iBAAiB,EAIvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAC/D,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAE/D;AACD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC;CACxC;AACD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAwBlE;AAOD,8FAA8F;AAC9F,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,OAAO,EACnB,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAKR;AACD,8FAA8F;AAC9F,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,YAAY,EACpB,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,iBAAiB,CAAC,CAkB5B;AACD,wBAAgB,kBAAkB,CAChC,cAAc,EAAE,OAAO,GACtB,MAAM,GAAG,SAAS,CAOpB;AACD,wGAAwG;AACxG,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAAC;CAChE,GAAG,MAAM,IAAI,CAkDb"}
package/dist/client.js ADDED
@@ -0,0 +1,125 @@
1
+ import { appRelativeUrl, displayMode, json, keys, record, text, toolName, } from './validation.js';
2
+ export { appRelativeUrl, OPENAI_EXTENSIONS_REVISION } from './validation.js';
3
+ /** Deployment/host-observation input, not an invented MCP wire capability. */
4
+ export function capabilityState(value) {
5
+ return value === 'present' || value === 'absent' ? value : 'unknown';
6
+ }
7
+ export function createOpenAiAppLink(options) {
8
+ json(options);
9
+ keys(record(options), [
10
+ 'pluginId',
11
+ 'toolName',
12
+ 'path',
13
+ 'marketplace',
14
+ 'platform',
15
+ ]);
16
+ const plugin = encodeURIComponent(text(options.pluginId, 256));
17
+ const tool = encodeURIComponent(text(options.toolName, 128));
18
+ const path = appRelativeUrl(options.path ?? '/');
19
+ const marketplace = options.marketplace === undefined
20
+ ? ''
21
+ : `@${encodeURIComponent(text(options.marketplace, 128))}`;
22
+ if (options.platform === 'web') {
23
+ if (marketplace)
24
+ throw new TypeError('Web links require a published ChatGPT plugin');
25
+ return `https://chatgpt.com/plugins/${plugin}/app/${tool}?path=${encodeURIComponent(path)}`;
26
+ }
27
+ if (options.platform !== 'desktop' && options.platform !== 'mobile')
28
+ throw new TypeError('Unsupported link platform');
29
+ return `${options.platform === 'desktop' ? 'codex' : 'chatgpt'}://plugins/${plugin}${marketplace}/app/${tool}?path=${encodeURIComponent(path)}`;
30
+ }
31
+ function appUrl(value) {
32
+ const parsed = new URL(text(value, 2048));
33
+ if (parsed.protocol !== 'https:' || parsed.username || parsed.password)
34
+ throw new TypeError('Expected credential-free HTTPS application URL');
35
+ return parsed.href;
36
+ }
37
+ /** No host-brand inference: absent/unverified support yields an ordinary application link. */
38
+ export function openAiNavigationLink(capability, link, fallbackUrl) {
39
+ const fallback = appUrl(fallbackUrl);
40
+ return capabilityState(capability) === 'present'
41
+ ? createOpenAiAppLink(link)
42
+ : fallback;
43
+ }
44
+ /** Display negotiation remains portable; unknown modes and host failures retain inline UI. */
45
+ export async function requestOpenAiDisplayMode(bridge, requested) {
46
+ let mode;
47
+ try {
48
+ mode = displayMode(requested);
49
+ }
50
+ catch {
51
+ return 'inline';
52
+ }
53
+ const snapshot = bridge.snapshot;
54
+ if (snapshot.state !== 'ready' ||
55
+ !(snapshot.hostContext.availableDisplayModes ?? ['inline']).includes(mode))
56
+ return 'inline';
57
+ try {
58
+ return displayMode(await bridge.requestDisplayMode(mode));
59
+ }
60
+ catch {
61
+ return 'inline';
62
+ }
63
+ }
64
+ export function readOpenAiDeepLink(rawHostContext) {
65
+ json(rawHostContext);
66
+ const context = record(rawHostContext);
67
+ if (context['openai/deepLink'] === undefined)
68
+ return undefined;
69
+ const link = record(context['openai/deepLink']);
70
+ keys(link, ['url']);
71
+ return appRelativeUrl(link.url);
72
+ }
73
+ /** Observe inert host context, then request a domain-authorized read; never route to arbitrary URLs. */
74
+ export function observeOpenAiNavigation(options) {
75
+ const name = toolName(options.resolveTool);
76
+ let last;
77
+ let generation = 0;
78
+ let disposed = false;
79
+ let pending;
80
+ const unsubscribe = options.bridge.subscribe((snapshot) => {
81
+ if (disposed || snapshot.state !== 'ready')
82
+ return;
83
+ let url;
84
+ try {
85
+ url = readOpenAiDeepLink(snapshot.rawHostContext);
86
+ }
87
+ catch {
88
+ last = undefined;
89
+ generation++;
90
+ pending?.abort();
91
+ options.onFallback('invalid');
92
+ return;
93
+ }
94
+ if (url === undefined || url === last)
95
+ return;
96
+ last = url;
97
+ const current = ++generation;
98
+ pending?.abort();
99
+ if (!snapshot.hostCapabilities.serverTools) {
100
+ options.onFallback('unavailable');
101
+ return;
102
+ }
103
+ pending = new AbortController();
104
+ void options.bridge.callTool(name, { url }, pending.signal).then((result) => {
105
+ if (disposed || current !== generation || options.bridge.signal.aborted)
106
+ return;
107
+ if (result.isError)
108
+ options.onFallback('denied');
109
+ else
110
+ options.onResult(result, url);
111
+ }, () => {
112
+ if (!disposed &&
113
+ current === generation &&
114
+ !options.bridge.signal.aborted)
115
+ options.onFallback('denied');
116
+ });
117
+ });
118
+ return () => {
119
+ disposed = true;
120
+ generation++;
121
+ pending?.abort();
122
+ unsubscribe();
123
+ };
124
+ }
125
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}