@happyvertical/smrt-app-mcp 0.51.36 → 0.51.38

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/AGENTS.md CHANGED
@@ -17,8 +17,9 @@ tool policy; `./sveltekit` owns the stateless Streamable HTTP transport.
17
17
  - Treat list/get tools as read-only; mutating tools require authentication unless the app deliberately wraps them with stronger policy.
18
18
  - Keep app policy outside generated tool schemas. The wrapper owns auth, allow-list, and workflow assertion behavior.
19
19
  - Return 404 for tools outside the app allow-list so private generated tools are not enumerated by mistake.
20
- - Trust only a principal supplied by the application adapter. This package does
21
- not validate bearer tokens or implement an OAuth authorization server.
20
+ - Trust only a principal supplied by the application adapter. This package
21
+ does not implement an OAuth authorization server. The opt-in `./auth` entry
22
+ validates JWT access tokens and requires an application-owned principal mapper.
22
23
  - Mount `mountMcpRoute` as one modern `POST` endpoint. It creates a fresh
23
24
  protocol server per request, emits no session id, and refuses subscriptions.
24
25
  - Keep tool catalogs private by default. Public caching requires an explicit
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  App-runtime MCP server scaffolding for s-m-r-t apps. Provides:
4
4
 
5
- - **Core** — `createMcpAppServer({ smrtOptions, serverInfo, allowedClassNames, publicToolPatterns?, toolListCache?, toolPolicy?, workflowAssertions? })` returning `{ listTools, callTool }` wired to `@happyvertical/smrt-core/generators/mcp`.
5
+ - **Core** — `createMcpAppServer({ smrtOptions, serverInfo, allowedClassNames, publicToolPatterns?, toolListCache?, toolPolicy?, workflowAssertions?, workflowTools? })` returning `{ listTools, callTool }` wired to `@happyvertical/smrt-core/generators/mcp`.
6
6
  - **SvelteKit adapters** (`./sveltekit`) — `mountMcpRoute` mounts a modern
7
7
  2026-07-28 stateless Streamable HTTP MCP endpoint. The REST-shaped
8
8
  `mountMcpToolsRoute` / `mountMcpCallRoute` aliases remain available for one
@@ -12,8 +12,11 @@ For piping a deployed app's MCP surface to a local stdio MCP client, see `@happy
12
12
 
13
13
  For public deployments, follow the
14
14
  [remote MCP authorization contract](../../docs/content/architecture/remote-mcp-authorization.md).
15
- This package trusts the principal supplied by the application adapter; it does
16
- not implement an OAuth authorization server or validate bearer tokens itself.
15
+ The server trusts the principal supplied by the application adapter. Validate
16
+ bearer tokens at an application gateway or with the server-only `./auth` JWT
17
+ adapter, then resolve fresh application membership and tenant authority on every
18
+ request. The OAuth issuer remains operator-owned; this package does not implement
19
+ an OAuth authorization server.
17
20
 
18
21
  ```ts
19
22
  // src/lib/server/mcp.ts
@@ -44,6 +47,39 @@ export const mcpServer = createMcpAppServer({
44
47
  });
45
48
  ```
46
49
 
50
+ `workflowTools` composes explicitly declared application workflows into this
51
+ same catalog. Every workflow must declare its canonical `effect`,
52
+ `idempotent`, and `openWorld` values; title, icons, and a portable UI resource
53
+ association are host presentation metadata only. The configured `toolPolicy`
54
+ and `workflowAssertions` run for both discovery and direct calls before the
55
+ workflow handler receives the trusted principal and arguments.
56
+ Authored names must be lowercase snake_case strings of at most 64 characters;
57
+ input and output schemas must both have object roots. Invalid declarations fail
58
+ at server construction. Ordinary handler exceptions return a generic `isError`
59
+ result with text and structured error data; internal exception details are not
60
+ exposed. Intentional `McpAccessError` denials retain the transport's access-error
61
+ contract.
62
+
63
+ ```ts
64
+ workflowTools: [{
65
+ name: 'application_prepare',
66
+ description: 'Prepare an application for human review',
67
+ title: 'Prepare application',
68
+ inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
69
+ outputSchema: { type: 'object', properties: { prepared: { type: 'boolean' } } },
70
+ effect: 'write', idempotent: true, openWorld: false,
71
+ ui: { resourceUri: 'ui://application/prepare.html', visibility: ['app'] },
72
+ async execute({ arguments: args, principal }) {
73
+ return { content: [{ type: 'text', text: 'Prepared for review.' }], structuredContent: { prepared: true } };
74
+ },
75
+ }],
76
+ ```
77
+
78
+ The resulting descriptor preserves `_meta.ui.resourceUri` and optional
79
+ `_meta.ui.visibility` through SDK-v2 `tools/list`, while ordinary clients retain the text and structured result.
80
+ The resource implementation itself is staged separately; metadata neither
81
+ loads a resource nor grants an application permission.
82
+
47
83
  ```ts
48
84
  // src/routes/api/mcp/+server.ts
49
85
  import { mountMcpRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
@@ -127,3 +163,77 @@ unauthenticated for both discovery and calls. If an older mount supplies only
127
163
  `resolveAuthenticated: () => true` and no principal, discovery keeps its old
128
164
  boolean behavior while calls remain user-less as before; migrate that mount to
129
165
  `resolvePrincipal` for one identity across both routes.
166
+
167
+ ## Prebuilt MCP Apps resources
168
+
169
+ Declare portable HTML resources alongside workflow tools. A URI must contain
170
+ its explicit version as a path segment; tool `ui.resourceUri` references must
171
+ resolve to a declared resource. The server snapshots UTF-8 bytes, a SHA-256
172
+ digest and portable MIME `text/html;profile=mcp-app` at construction time. It
173
+ never compiles source or inserts principal data into templates.
174
+
175
+ ```ts
176
+ const server = createMcpAppServer({
177
+ smrtOptions: () => ({ db }),
178
+ serverInfo: { name: 'application', version: '1' },
179
+ allowedClassNames: [],
180
+ resources: [{
181
+ uri: 'ui://application/v1/view.html',
182
+ version: 'v1',
183
+ name: 'Application view',
184
+ html: prebuiltHtml,
185
+ }],
186
+ workflowTools: [viewWorkflow], // ui.resourceUri points at the declaration
187
+ resourcePolicy: ({ principal }) =>
188
+ principal?.id === ownerId && principal?.tenantId === activeTenantId,
189
+ });
190
+ ```
191
+
192
+ Resources are private by default and require a stable principal id plus an explicit
193
+ `resourcePolicy`; an omitted policy denies both listing and reads. Only an
194
+ explicit `public: true` declaration permits anonymous reads of a static
195
+ artifact. `resourcePolicy` runs afresh for both catalog and direct reads; errors
196
+ fail closed. Every associated tool must also pass the ordinary tool policy.
197
+ Unknown and denied URIs return the same error. The resource policy owns tenant
198
+ and owner restrictions; static templates contain no candidate records, tokens
199
+ or sessions. Retrieve changing data through authorized tools instead.
200
+
201
+ Optional `metadata` carries extension JSON into catalog/read `_meta`, with a
202
+ 64 KiB and 16-level limit. Non-JSON values, accessors, cycles and reserved `ui`
203
+ or `com.happyvertical.smrt/resource` keys are rejected. Metadata grants no access.
204
+
205
+ The raw HTML budget is 100 KiB. CSP connection, resource, frame and base origins
206
+ default to empty lists; permissions default to none. Origins must be exact HTTPS
207
+ origins (WSS is additionally accepted for connections). Unknown CSP/permission
208
+ fields are rejected. Bundle assets inline or use literal absolute references to
209
+ explicitly declared resource/frame origins. Relative asset references,
210
+ CSS imports/escapes and alternate image/source functions, base/object/embed/meta tags, srcset and srcdoc are rejected
211
+ by the conservative static validation profile. HTML is parsed before validation;
212
+ script raw text and inert comments are not CSS. Decoded attribute URLs must still
213
+ match declared origins. Raster data images are permitted.
214
+ Dynamic JavaScript networking requires host CSP enforcement; declarations are
215
+ not a JavaScript sandbox. Host rendering and enforcement remain M4/M8 gates.
216
+
217
+ `mountMcpRoute` exposes native `resources/list` and `resources/read`, with
218
+ private, zero-TTL results and no subscriptions or HTTP sessions. Missing UI
219
+ capabilities do not remove ordinary text/structured tool results. The app CLI
220
+ bridge now defaults to this modern `/api/mcp` endpoint; use explicit
221
+ `transport: 'legacy-rest'` (or the generic bin's `--legacy-rest`) only during a
222
+ migration from `/api/mcp/tools` and `/api/mcp/call`. The modern bridge uses SDK v2,
223
+ re-resolves credentials per request, refuses redirects, and forwards resource
224
+ and tool metadata without inventing UI or extension capabilities.
225
+
226
+ Generated tool allow-lists use the generator-owned original class identity, even
227
+ when the advertised name is a canonical alias. Guards keyed by either the alias
228
+ or original tool name run before both direct and task dispatch. Authored workflows
229
+ retain their explicit names and effect policy; catalogs containing authored
230
+ workflows keep private cache scope because they have no generated tenant identity.
231
+
232
+ ## Remote JWT authorization
233
+
234
+ The server-only `@happyvertical/smrt-app-mcp/auth` entry exports
235
+ `createMcpResourceAuth` for protected-resource metadata, bearer challenges and
236
+ JWT verification against a configured existing issuer. A required application
237
+ callback rechecks actor and active tenant membership per request. See the
238
+ [remote authorization contract](../../docs/content/architecture/remote-mcp-authorization.md)
239
+ for route wiring, local/hosted profiles and the explicit JWT revocation limits.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /** Server-only OAuth protected-resource boundary. This is not an issuer. */
2
+ import { type JWTPayload } from 'jose';
3
+ import type { McpAppPrincipal } from './server.js';
4
+ export type McpDeploymentProfile = 'local' | 'self-hosted' | 'cloud';
5
+ export interface McpVerifiedIdentity {
6
+ readonly issuer: string;
7
+ readonly subject: string;
8
+ readonly scopes: readonly string[];
9
+ readonly claims: Readonly<JWTPayload>;
10
+ }
11
+ export interface McpPrincipalMapping {
12
+ id: string;
13
+ tenantId?: string;
14
+ kind?: string;
15
+ roles?: string[];
16
+ }
17
+ export interface McpResourceAuthOptions {
18
+ profile: McpDeploymentProfile;
19
+ /** Exact public resource identifier; also the required token audience. */
20
+ resource: string;
21
+ /** Exact issuer identifier, including any trailing slash. */
22
+ issuer: string;
23
+ /** Operator-configured signing keys. Never derived from an untrusted token. */
24
+ jwksUri: string;
25
+ scopes: readonly string[];
26
+ algorithms: readonly ('RS256' | 'ES256')[];
27
+ /** Default at+jwt. Select JWT only for a provider that uses it for access tokens. */
28
+ tokenType?: 'at+jwt' | 'JWT';
29
+ /** Default true. Set false only for an explicitly single-tenant application. */
30
+ requireTenant?: boolean;
31
+ /** Re-run for every request; return null for revoked/disabled/unmapped users.
32
+ * Resolve active tenant/membership in trusted application state, never request
33
+ * arguments. Provider JWT revocation is NOT detected by offline validation.
34
+ */
35
+ resolvePrincipal(identity: McpVerifiedIdentity): Promise<McpPrincipalMapping | null>;
36
+ }
37
+ export interface McpResourceAuth {
38
+ /** Mount GET at this URL. Paths use RFC 9728's well-known path insertion. */
39
+ readonly metadataUrl: string;
40
+ metadataResponse(): Response;
41
+ /** A failure response contains no token, claims or upstream error details. */
42
+ authenticate(request: Request): Promise<{
43
+ ok: true;
44
+ principal: McpAppPrincipal & {
45
+ id: string;
46
+ };
47
+ } | {
48
+ ok: false;
49
+ response: Response;
50
+ }>;
51
+ }
52
+ export declare function createMcpResourceAuth(options: McpResourceAuthOptions): McpResourceAuth;
53
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,OAAO,EAAsB,KAAK,UAAU,EAAa,MAAM,MAAM,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,aAAa,GAAG,OAAO,CAAC;AACrE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;CACvC;AACD,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AACD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,oBAAoB,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,UAAU,EAAE,SAAS,CAAC,OAAO,GAAG,OAAO,CAAC,EAAE,CAAC;IAC3C,qFAAqF;IACrF,SAAS,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC7B,gFAAgF;IAChF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CACd,QAAQ,EAAE,mBAAmB,GAC5B,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;CACxC;AAqCD,MAAM,WAAW,eAAe;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,gBAAgB,IAAI,QAAQ,CAAC;IAC7B,8EAA8E;IAC9E,YAAY,CACV,OAAO,EAAE,OAAO,GACf,OAAO,CACN;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,SAAS,EAAE,eAAe,GAAG;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,GACzD;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,CACpC,CAAC;CACH;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CA0HjB"}