@happyvertical/smrt-template-sveltekit 0.52.0 → 0.53.1
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 +2 -1
- package/README.md +12 -2
- package/index.js +8 -0
- package/mcp-apps-template/package.dependencies.json +4 -0
- package/mcp-apps-template/src/lib/McpAppsBridge.svelte +37 -0
- package/mcp-apps-template/src/lib/server/mcp-hosted-principal.ts +12 -0
- package/mcp-apps-template/src/lib/server/mcp-hosted.ts +28 -0
- package/mcp-apps-template/src/lib/server/mcp.ts +85 -0
- package/mcp-apps-template/src/routes/.well-known/oauth-protected-resource/api/mcp/+server.ts +7 -0
- package/mcp-apps-template/src/routes/api/mcp/+server.ts +15 -0
- package/package.json +19 -16
- package/template/Dockerfile +8 -6
- package/template/README.md +40 -1
- package/template/package.json +14 -13
- package/template/pnpm-workspace.yaml +3 -0
- package/template/scripts/smrt-app.mjs +2 -2
- package/template/scripts/smrt-mcp-apps.mjs +18 -0
- package/template/scripts/smrt-runtime-identity.mjs +6 -0
- package/template.config.js +75 -0
package/AGENTS.md
CHANGED
|
@@ -13,7 +13,8 @@ It is the ground-up alternative to `smrt-saas-starter`.
|
|
|
13
13
|
|
|
14
14
|
## Current generated-project contract
|
|
15
15
|
|
|
16
|
-
- Node `>=
|
|
16
|
+
- Node `>=26.0.0`; pnpm `11.25.0` via `packageManager` and `engines`. Node 25+
|
|
17
|
+
images ship no Corepack, so the Dockerfile installs a pinned copy.
|
|
17
18
|
- `runtime.profile` is the canonical infrastructure selector. Generated apps
|
|
18
19
|
expose deterministic `app:*` operations and keep runtime state outside source.
|
|
19
20
|
- The production baseline uses adapter-node with separate web, task-worker, and
|
package/README.md
CHANGED
|
@@ -20,9 +20,19 @@ Run the copy command from the monorepo root. The public 0.43.10 CLI advertises
|
|
|
20
20
|
`gnode create`, but its command dispatcher currently rejects `gnode`; this
|
|
21
21
|
package's tested `copyTemplate()` export is the working scaffold path.
|
|
22
22
|
|
|
23
|
-
Generated projects require Node.js
|
|
23
|
+
Generated projects require Node.js 26.0.0 or newer and pnpm 11.25.0. They pin
|
|
24
24
|
all directly used `@happyvertical/smrt-*` packages to the current monorepo
|
|
25
|
-
release (synced at publish by
|
|
25
|
+
release, including opt-in MCP Apps dependencies (synced at publish by
|
|
26
|
+
`scripts/sync-template-versions.mjs`; `--check` detects drift).
|
|
27
|
+
|
|
28
|
+
`smrt gnode create <name> --mcp-apps` supports installed, local, and Git
|
|
29
|
+
subdirectory templates. Plugin names must satisfy the pinned Agent Plugins
|
|
30
|
+
1.0.0 schema before generation starts. `smrt mcp-apps validate` applies the
|
|
31
|
+
official closed plugin/MCP schemas offline, then s-m-r-t endpoint and credential
|
|
32
|
+
checks. Every existing scoped registry directive must be canonical; inline
|
|
33
|
+
`#` and `;` comments are supported. Hosted MCP caches its successfully created
|
|
34
|
+
authenticator for the process, preserving JWKS keys across requests while
|
|
35
|
+
principal resolution and task authorization still run for each request.
|
|
26
36
|
|
|
27
37
|
For programmatic scaffolding:
|
|
28
38
|
|
package/index.js
CHANGED
|
@@ -80,6 +80,14 @@ export function copyTemplate(destination, options = {}) {
|
|
|
80
80
|
if (!existsSync(destination)) {
|
|
81
81
|
mkdirSync(destination, { recursive: true });
|
|
82
82
|
}
|
|
83
|
+
if (!pathEntryExists(join(destination, '.npmrc'))) writeFileSync(join(destination, '.npmrc'), '@happyvertical:registry=https://npm.happyvertical.com/\n');
|
|
84
|
+
else {
|
|
85
|
+
const npmrc = join(destination, '.npmrc');
|
|
86
|
+
if (!lstatSync(npmrc).isFile()) throw new Error('Generated application .npmrc must be a regular file');
|
|
87
|
+
const current = readFileSync(npmrc, 'utf8'), routes = [...current.matchAll(/^\s*@happyvertical:registry\s*=\s*(\S+)(?:\s+[;#].*)?\s*$/gm)].map((match) => match[1]);
|
|
88
|
+
if (routes.some((route) => route !== 'https://npm.happyvertical.com/')) throw new Error('Generated application .npmrc has a conflicting @happyvertical registry');
|
|
89
|
+
if (!routes.length) writeFileSync(npmrc, `${current}${current.endsWith('\n') ? '' : '\n'}@happyvertical:registry=https://npm.happyvertical.com/\n`);
|
|
90
|
+
}
|
|
83
91
|
|
|
84
92
|
// Copy all template files, skipping internal-only directories
|
|
85
93
|
cpSync(templatePath, destination, {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { requestOpenAiDisplayMode } from '@happyvertical/smrt-mcp-openai/client';
|
|
3
|
+
import { useMcpApp } from '@happyvertical/smrt-svelte/mcp-apps';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Exact, application-configured host origin. Never derive this from URL input. */
|
|
7
|
+
hostOrigin: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { hostOrigin }: Props = $props();
|
|
11
|
+
let expandUnavailable = $state(false);
|
|
12
|
+
const app = useMcpApp(() => ({
|
|
13
|
+
appInfo: { name: 'smrt-app', version: '0.1.0' },
|
|
14
|
+
availableDisplayModes: ['inline', 'fullscreen'],
|
|
15
|
+
hostOrigin,
|
|
16
|
+
hostWindow: parent,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
async function expand() {
|
|
20
|
+
try {
|
|
21
|
+
if (!app.bridge) throw new Error('Host bridge unavailable');
|
|
22
|
+
await requestOpenAiDisplayMode(app.bridge, 'fullscreen');
|
|
23
|
+
} catch {
|
|
24
|
+
expandUnavailable = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<section aria-labelledby="mcp-apps-title" class="mcp-apps-bridge">
|
|
30
|
+
<h1 id="mcp-apps-title">Items</h1>
|
|
31
|
+
<p>Use this view to review the items your signed-in tenant authorizes.</p>
|
|
32
|
+
{#if app.error || expandUnavailable}
|
|
33
|
+
<p role="status">Host controls are unavailable; this view remains inline.</p>
|
|
34
|
+
{:else}
|
|
35
|
+
<button type="button" onclick={expand}>Expand view</button>
|
|
36
|
+
{/if}
|
|
37
|
+
</section>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpVerifiedIdentity, McpPrincipalMapping } from '@happyvertical/smrt-app-mcp/auth';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deployments must replace this binding with a database-backed current-account
|
|
5
|
+
* and active-membership lookup. Token claims never select a tenant.
|
|
6
|
+
*/
|
|
7
|
+
export type HostedMcpPrincipalResolver = (
|
|
8
|
+
identity: McpVerifiedIdentity,
|
|
9
|
+
) => Promise<McpPrincipalMapping | null>;
|
|
10
|
+
|
|
11
|
+
export const resolveHostedMcpPrincipal: HostedMcpPrincipalResolver | undefined =
|
|
12
|
+
undefined;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createMcpResourceAuth } from '@happyvertical/smrt-app-mcp/auth';
|
|
2
|
+
import { applicationRuntime } from './application-runtime.js';
|
|
3
|
+
import { resolveHostedMcpPrincipal } from './mcp-hosted-principal.js';
|
|
4
|
+
|
|
5
|
+
function required(name: string): string {
|
|
6
|
+
const value = process.env[name]?.trim();
|
|
7
|
+
if (!value) throw new Error(`Hosted MCP requires ${name}.`);
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let cachedAuth: ReturnType<typeof createMcpResourceAuth> | undefined;
|
|
12
|
+
|
|
13
|
+
export function hostedMcpAuth() {
|
|
14
|
+
if (applicationRuntime.profile === 'local') return null;
|
|
15
|
+
if (cachedAuth) return cachedAuth;
|
|
16
|
+
if (!resolveHostedMcpPrincipal)
|
|
17
|
+
throw new Error('Hosted MCP requires an application-owned resolveHostedMcpPrincipal binding.');
|
|
18
|
+
cachedAuth = createMcpResourceAuth({
|
|
19
|
+
profile: applicationRuntime.profile,
|
|
20
|
+
resource: required('SMRT_MCP_RESOURCE'),
|
|
21
|
+
issuer: required('SMRT_MCP_ISSUER'),
|
|
22
|
+
jwksUri: required('SMRT_MCP_JWKS_URI'),
|
|
23
|
+
scopes: required('SMRT_MCP_SCOPES').split(/\s+/u),
|
|
24
|
+
algorithms: ['RS256'],
|
|
25
|
+
resolvePrincipal: resolveHostedMcpPrincipal,
|
|
26
|
+
});
|
|
27
|
+
return cachedAuth;
|
|
28
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMcpAppServer,
|
|
3
|
+
type McpAppPrincipal,
|
|
4
|
+
type McpWorkflowToolDefinition,
|
|
5
|
+
} from '@happyvertical/smrt-app-mcp';
|
|
6
|
+
import { openAiDisplayMetadata, withOpenAiEntrypoints } from '@happyvertical/smrt-mcp-openai';
|
|
7
|
+
import { getApplicationDatabaseConfig } from './application-runtime.js';
|
|
8
|
+
|
|
9
|
+
const ITEM_RESOURCE_URI = 'ui://smrt-app/v1/items.html';
|
|
10
|
+
const itemResourceHtml = `<!doctype html><html lang="en"><head><title>Items</title></head><body><main tabindex="-1"><h1>Items</h1><p>Use the host controls to review your authorized items.</p></main></body></html>`;
|
|
11
|
+
|
|
12
|
+
const itemsOverview: McpWorkflowToolDefinition = withOpenAiEntrypoints(
|
|
13
|
+
{
|
|
14
|
+
name: 'items_overview',
|
|
15
|
+
description: 'Open the authorized item overview.',
|
|
16
|
+
title: 'Items',
|
|
17
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
18
|
+
outputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: { state: { type: 'string' } },
|
|
21
|
+
required: ['state'],
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
},
|
|
24
|
+
effect: 'read',
|
|
25
|
+
idempotent: true,
|
|
26
|
+
openWorld: false,
|
|
27
|
+
ui: { resourceUri: ITEM_RESOURCE_URI },
|
|
28
|
+
async execute() {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: 'text', text: 'Item overview is ready.' }],
|
|
31
|
+
structuredContent: { state: 'ready' },
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
['global'],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The session hook resolves membership and permissions for every request. This
|
|
40
|
+
* adapter only projects that trusted, request-local result; headers and route
|
|
41
|
+
* arguments are never identity inputs.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveMcpPrincipal(event: {
|
|
44
|
+
locals?: Record<string, unknown>;
|
|
45
|
+
}): McpAppPrincipal | null {
|
|
46
|
+
const user = event.locals?.user as { id?: unknown } | undefined;
|
|
47
|
+
const tenantId = event.locals?.tenantId;
|
|
48
|
+
const permissions = event.locals?.permissions;
|
|
49
|
+
if (typeof user?.id !== 'string' || typeof tenantId !== 'string') return null;
|
|
50
|
+
if (!Array.isArray(permissions) || !permissions.every((value) => typeof value === 'string'))
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
id: user.id,
|
|
54
|
+
tenantId,
|
|
55
|
+
kind: 'human',
|
|
56
|
+
scopes: [...permissions].sort(),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const mcpServer = createMcpAppServer({
|
|
61
|
+
smrtOptions: () => ({ db: getApplicationDatabaseConfig() }),
|
|
62
|
+
serverInfo: { name: 'smrt-app', version: '0.1.0' },
|
|
63
|
+
allowedClassNames: ['Item'],
|
|
64
|
+
workflowTools: [itemsOverview],
|
|
65
|
+
resources: [
|
|
66
|
+
{
|
|
67
|
+
uri: ITEM_RESOURCE_URI,
|
|
68
|
+
version: 'v1',
|
|
69
|
+
name: 'Items',
|
|
70
|
+
html: itemResourceHtml,
|
|
71
|
+
csp: {},
|
|
72
|
+
metadata: openAiDisplayMetadata({ availableDisplayModes: ['inline'] }),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
toolPolicy: ({ principal }) =>
|
|
76
|
+
principal?.kind === 'human' &&
|
|
77
|
+
typeof principal.id === 'string' &&
|
|
78
|
+
typeof principal.tenantId === 'string' &&
|
|
79
|
+
principal.scopes?.includes('items.read') === true,
|
|
80
|
+
resourcePolicy: ({ principal }) =>
|
|
81
|
+
principal?.kind === 'human' &&
|
|
82
|
+
typeof principal.id === 'string' &&
|
|
83
|
+
typeof principal.tenantId === 'string' &&
|
|
84
|
+
principal.scopes?.includes('items.read') === true,
|
|
85
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { hostedMcpAuth } from '$lib/server/mcp-hosted';
|
|
2
|
+
|
|
3
|
+
/** Serve exactly the RFC 9728 metadata URL advertised by hosted bearer challenges. */
|
|
4
|
+
export function GET() {
|
|
5
|
+
const auth = hostedMcpAuth();
|
|
6
|
+
return auth?.metadataResponse() ?? new Response(null, { status: 404 });
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mountMcpRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
|
|
2
|
+
|
|
3
|
+
import { mcpServer, resolveMcpPrincipal } from '$lib/server/mcp';
|
|
4
|
+
import { hostedMcpAuth } from '$lib/server/mcp-hosted';
|
|
5
|
+
|
|
6
|
+
/** Stateless SDK-v2 Streamable HTTP MCP endpoint. */
|
|
7
|
+
const localRoute = mountMcpRoute(mcpServer, { resolvePrincipal: resolveMcpPrincipal });
|
|
8
|
+
|
|
9
|
+
export async function POST(event: Parameters<typeof localRoute>[0]) {
|
|
10
|
+
const auth = hostedMcpAuth();
|
|
11
|
+
if (!auth) return localRoute(event);
|
|
12
|
+
const checked = await auth.authenticate(event.request);
|
|
13
|
+
if (!checked.ok) return checked.response;
|
|
14
|
+
return mountMcpRoute(mcpServer, { resolvePrincipal: () => checked.principal })(event);
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-template-sveltekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.1",
|
|
4
4
|
"description": "Minimal SvelteKit project template with s-m-r-t framework integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./index.js",
|
|
9
|
+
"./template.config.js": "./template.config.js",
|
|
9
10
|
"./template/*": "./template/*"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"CLAUDE.md",
|
|
13
14
|
"README.md",
|
|
14
15
|
"index.js",
|
|
16
|
+
"template.config.js",
|
|
15
17
|
"template",
|
|
16
|
-
"AGENTS.md"
|
|
18
|
+
"AGENTS.md",
|
|
19
|
+
"mcp-apps-template"
|
|
17
20
|
],
|
|
18
21
|
"keywords": [
|
|
19
22
|
"smrt",
|
|
@@ -29,22 +32,22 @@
|
|
|
29
32
|
"directory": "packages/template-sveltekit"
|
|
30
33
|
},
|
|
31
34
|
"peerDependencies": {
|
|
32
|
-
"@happyvertical/smrt-core": "0.
|
|
35
|
+
"@happyvertical/smrt-core": "0.53.1"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
|
-
"@happyvertical/smrt-app-runtime": "0.
|
|
36
|
-
"@happyvertical/smrt-assets": "0.
|
|
37
|
-
"@happyvertical/smrt-cli": "0.
|
|
38
|
-
"@happyvertical/smrt-config": "0.
|
|
39
|
-
"@happyvertical/smrt-jobs": "0.
|
|
40
|
-
"@happyvertical/smrt-playbooks": "0.
|
|
41
|
-
"@happyvertical/smrt-profiles": "0.
|
|
42
|
-
"@happyvertical/smrt-scanner": "0.
|
|
43
|
-
"@happyvertical/smrt-svelte": "0.
|
|
44
|
-
"@happyvertical/smrt-tenancy": "0.
|
|
45
|
-
"@happyvertical/smrt-ui": "0.
|
|
46
|
-
"@happyvertical/smrt-users": "0.
|
|
47
|
-
"@happyvertical/smrt-web": "0.
|
|
38
|
+
"@happyvertical/smrt-app-runtime": "0.53.1",
|
|
39
|
+
"@happyvertical/smrt-assets": "0.53.1",
|
|
40
|
+
"@happyvertical/smrt-cli": "0.53.1",
|
|
41
|
+
"@happyvertical/smrt-config": "0.53.1",
|
|
42
|
+
"@happyvertical/smrt-jobs": "0.53.1",
|
|
43
|
+
"@happyvertical/smrt-playbooks": "0.53.1",
|
|
44
|
+
"@happyvertical/smrt-profiles": "0.53.1",
|
|
45
|
+
"@happyvertical/smrt-scanner": "0.53.1",
|
|
46
|
+
"@happyvertical/smrt-svelte": "0.53.1",
|
|
47
|
+
"@happyvertical/smrt-tenancy": "0.53.1",
|
|
48
|
+
"@happyvertical/smrt-ui": "0.53.1",
|
|
49
|
+
"@happyvertical/smrt-users": "0.53.1",
|
|
50
|
+
"@happyvertical/smrt-web": "0.53.1",
|
|
48
51
|
"@happyvertical/sql": "^0.89.12",
|
|
49
52
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
50
53
|
"@playwright/test": "^1.63.0",
|
package/template/Dockerfile
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
FROM node:
|
|
1
|
+
FROM node:26.9.0-alpine@sha256:dbaa92e5758cbbcf85d65d5403fdb530fe3442cbe8c6dbfb7ef23365450d5070 AS build
|
|
2
2
|
WORKDIR /app
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# Node 25+ images no longer bundle Corepack; a pinned copy keeps
|
|
4
|
+
# `packageManager` authoritative for the pnpm version.
|
|
5
|
+
RUN npm install --global corepack@0.36.0 && corepack enable
|
|
6
|
+
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./
|
|
5
7
|
RUN pnpm install --frozen-lockfile
|
|
6
8
|
COPY . .
|
|
7
9
|
RUN pnpm build
|
|
8
10
|
|
|
9
|
-
FROM node:
|
|
11
|
+
FROM node:26.9.0-alpine@sha256:dbaa92e5758cbbcf85d65d5403fdb530fe3442cbe8c6dbfb7ef23365450d5070 AS runtime
|
|
10
12
|
WORKDIR /app
|
|
11
|
-
RUN corepack enable
|
|
12
|
-
COPY --from=build /app/package.json /app/pnpm-lock.yaml* ./
|
|
13
|
+
RUN npm install --global corepack@0.36.0 && corepack enable
|
|
14
|
+
COPY --from=build /app/package.json /app/pnpm-lock.yaml* /app/pnpm-workspace.yaml ./
|
|
13
15
|
RUN pnpm install --frozen-lockfile --prod=false
|
|
14
16
|
ENV NODE_ENV=production
|
|
15
17
|
ENV SMRT_RUNTIME_PROFILE=self-hosted
|
package/template/README.md
CHANGED
|
@@ -6,7 +6,7 @@ managed-cloud environments. It does not provision external providers.
|
|
|
6
6
|
|
|
7
7
|
## 1. Install and run
|
|
8
8
|
|
|
9
|
-
Requirements: Node.js
|
|
9
|
+
Requirements: Node.js 26.0.0 or newer and pnpm 11.25.0. The exact pnpm version
|
|
10
10
|
is declared in `packageManager`.
|
|
11
11
|
|
|
12
12
|
```bash
|
|
@@ -222,6 +222,32 @@ After a mutation, call `invalidate('smrt:items')`. Only loads that declared
|
|
|
222
222
|
|
|
223
223
|
## 8. Use generated REST, MCP, WebMCP, and CLI interfaces
|
|
224
224
|
|
|
225
|
+
## Optional portable MCP Apps packaging
|
|
226
|
+
|
|
227
|
+
The ordinary scaffold does not create a plugin package or add a remote endpoint.
|
|
228
|
+
When you create a project with `smrt gnode create <name> --mcp-apps`, it adds
|
|
229
|
+
`mcp-apps/plugin.json` and `mcp-apps/mcp.json`. The initial server points only
|
|
230
|
+
at loopback (`http://127.0.0.1:3000/api/mcp`) so it cannot silently publish a
|
|
231
|
+
local instance. Run `pnpm mcp-apps:validate` in the generated application after
|
|
232
|
+
editing package metadata. It rejects symlinks, traversal paths, missing or linked UI assets (including screenshots),
|
|
233
|
+
non-object or malformed manifests, credential-shaped JSON fields and credential carrier files,
|
|
234
|
+
URL userinfo, non-portable schemas, and non-loopback HTTP server URLs. Diagnostics never
|
|
235
|
+
include malformed manifest content.
|
|
236
|
+
|
|
237
|
+
The option stages `src/routes/api/mcp/+server.ts`, a session-authorized `mcp`
|
|
238
|
+
server, and a bounded static resource with a restrictive CSP. It also includes
|
|
239
|
+
`McpAppsBridge.svelte` for an application-configured trusted host origin and
|
|
240
|
+
the optional OpenAI display adapter; it stays inert until your UI mounts it.
|
|
241
|
+
The scaffold does not enable remote MCP tasks, so a deployment that adds them
|
|
242
|
+
must supply the durable worker's live authorization callback before publication.
|
|
243
|
+
|
|
244
|
+
Before changing that URL, mount the native SDK v2 Streamable HTTP endpoint and
|
|
245
|
+
configure the application gateway's verified principal, issuer, audience and
|
|
246
|
+
scopes. Plugin metadata is an install surface only; it neither authenticates a
|
|
247
|
+
request nor grants tenant or workflow authority. Register `.app.json` only
|
|
248
|
+
after the host creates an eligible server ID; do not copy runtime extension
|
|
249
|
+
metadata into the portable manifest.
|
|
250
|
+
|
|
225
251
|
The Item configuration generates:
|
|
226
252
|
|
|
227
253
|
- REST: `GET`/`POST /api/items` and
|
|
@@ -375,3 +401,16 @@ production startup must set an explicit loopback `HOST`, and `app:start` is the
|
|
|
375
401
|
recommended entry point. Stop the app before backup/import. For deployed import, stop
|
|
376
402
|
web/workers and set `SMRT_MAINTENANCE_MODE=true`. Extend
|
|
377
403
|
`scripts/smrt-portability.mjs` for domain-specific transformations.
|
|
404
|
+
|
|
405
|
+
### Hosted MCP authorization
|
|
406
|
+
|
|
407
|
+
The opt-in MCP route uses the local signed session only in the `local` profile.
|
|
408
|
+
For `self-hosted` and `cloud`, configure HTTPS `SMRT_MCP_RESOURCE`,
|
|
409
|
+
`SMRT_MCP_ISSUER`, `SMRT_MCP_JWKS_URI`, and space-separated
|
|
410
|
+
`SMRT_MCP_SCOPES` values. Bind `resolveHostedMcpPrincipal` in
|
|
411
|
+
`src/lib/server/mcp-hosted-principal.ts` to an application-owned lookup that
|
|
412
|
+
checks the current account and active tenant membership on every request. It
|
|
413
|
+
must return `null` for disabled, revoked, or unmapped identities. The route
|
|
414
|
+
does not derive tenant authority from JWT claims, request headers, or tool
|
|
415
|
+
arguments; missing configuration or a missing binding fails closed before MCP
|
|
416
|
+
dispatch.
|
package/template/package.json
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.25.0",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=
|
|
8
|
+
"node": ">=26.0.0",
|
|
9
9
|
"pnpm": "11.25.0"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"dev": "node --env-file-if-exists=.env scripts/smrt-vite.mjs dev",
|
|
13
|
-
"build": "node --env-file-if-exists=.env scripts/smrt-vite.mjs build",
|
|
13
|
+
"build": "node scripts/smrt-mcp-apps.mjs validate-if-present && node --env-file-if-exists=.env scripts/smrt-vite.mjs build",
|
|
14
14
|
"preview": "node scripts/smrt-app.mjs start",
|
|
15
15
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
16
16
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"app:backup": "node scripts/smrt-app.mjs backup",
|
|
26
26
|
"app:export": "node scripts/smrt-app.mjs export",
|
|
27
27
|
"app:import": "node scripts/smrt-app.mjs import",
|
|
28
|
+
"mcp-apps:validate": "pnpm exec smrt mcp-apps validate --output-dir ./mcp-apps",
|
|
28
29
|
"worker": "node scripts/smrt-worker.mjs task",
|
|
29
30
|
"worker:schedule": "node scripts/smrt-worker.mjs schedule"
|
|
30
31
|
},
|
|
@@ -37,19 +38,19 @@
|
|
|
37
38
|
"svelte-check": "^4.7.6",
|
|
38
39
|
"typescript": "6.0.3",
|
|
39
40
|
"vite": "8.3.0",
|
|
40
|
-
"@happyvertical/smrt-cli": "^0.
|
|
41
|
+
"@happyvertical/smrt-cli": "^0.53.1"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
|
-
"@happyvertical/smrt-app-runtime": "^0.
|
|
44
|
-
"@happyvertical/smrt-config": "^0.
|
|
45
|
-
"@happyvertical/smrt-core": "^0.
|
|
46
|
-
"@happyvertical/smrt-jobs": "^0.
|
|
47
|
-
"@happyvertical/smrt-profiles": "^0.
|
|
48
|
-
"@happyvertical/smrt-svelte": "^0.
|
|
49
|
-
"@happyvertical/smrt-tenancy": "^0.
|
|
50
|
-
"@happyvertical/smrt-ui": "^0.
|
|
51
|
-
"@happyvertical/smrt-users": "^0.
|
|
52
|
-
"@happyvertical/smrt-web": "^0.
|
|
44
|
+
"@happyvertical/smrt-app-runtime": "^0.53.1",
|
|
45
|
+
"@happyvertical/smrt-config": "^0.53.1",
|
|
46
|
+
"@happyvertical/smrt-core": "^0.53.1",
|
|
47
|
+
"@happyvertical/smrt-jobs": "^0.53.1",
|
|
48
|
+
"@happyvertical/smrt-profiles": "^0.53.1",
|
|
49
|
+
"@happyvertical/smrt-svelte": "^0.53.1",
|
|
50
|
+
"@happyvertical/smrt-tenancy": "^0.53.1",
|
|
51
|
+
"@happyvertical/smrt-ui": "^0.53.1",
|
|
52
|
+
"@happyvertical/smrt-users": "^0.53.1",
|
|
53
|
+
"@happyvertical/smrt-web": "^0.53.1",
|
|
53
54
|
"@happyvertical/sql": "^0.89.12",
|
|
54
55
|
"@modelcontextprotocol/server": "^2.0.0"
|
|
55
56
|
}
|
|
@@ -545,11 +545,11 @@ async function doctor() {
|
|
|
545
545
|
});
|
|
546
546
|
}
|
|
547
547
|
|
|
548
|
-
if (Number(process.versions.node.split('.')[0]) <
|
|
548
|
+
if (Number(process.versions.node.split('.')[0]) < 26) {
|
|
549
549
|
findings.push({
|
|
550
550
|
code: 'unsupported-node',
|
|
551
551
|
severity: 'error',
|
|
552
|
-
message: 'Node.js
|
|
552
|
+
message: 'Node.js 26 or newer is required.',
|
|
553
553
|
recovery: 'Install the Node.js version declared in package.json engines.',
|
|
554
554
|
});
|
|
555
555
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Validate opt-in portable MCP Apps metadata before an application build. */
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
if (process.argv[2] !== 'validate-if-present') {
|
|
6
|
+
throw new Error('Usage: smrt-mcp-apps.mjs validate-if-present');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!existsSync('mcp-apps')) process.exit(0);
|
|
10
|
+
|
|
11
|
+
const command = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
|
12
|
+
const result = spawnSync(
|
|
13
|
+
command,
|
|
14
|
+
['exec', 'smrt', 'mcp-apps', 'validate', '--output-dir', './mcp-apps'],
|
|
15
|
+
{ stdio: 'inherit' },
|
|
16
|
+
);
|
|
17
|
+
if (result.error) throw result.error;
|
|
18
|
+
if (result.status !== 0) process.exit(result.status ?? 1);
|
|
@@ -106,6 +106,12 @@ export function runtimeConfigurationFingerprint(
|
|
|
106
106
|
assets: environment.SMRT_ASSETS_READINESS_MODULE || null,
|
|
107
107
|
secrets: environment.SMRT_SECRETS_READINESS_MODULE || null,
|
|
108
108
|
},
|
|
109
|
+
mcpAuthorization: {
|
|
110
|
+
resource: environment.SMRT_MCP_RESOURCE || null,
|
|
111
|
+
issuer: environment.SMRT_MCP_ISSUER || null,
|
|
112
|
+
jwksUri: environment.SMRT_MCP_JWKS_URI || null,
|
|
113
|
+
scopes: environment.SMRT_MCP_SCOPES || null,
|
|
114
|
+
},
|
|
109
115
|
}),
|
|
110
116
|
)
|
|
111
117
|
.digest('hex');
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvelteKit Template Configuration
|
|
3
|
+
*
|
|
4
|
+
* This file configures the template for use with `smrt gnode create`
|
|
5
|
+
*
|
|
6
|
+
* Version pins are kept in sync with `template/package.json` and the rest of
|
|
7
|
+
* the s-m-r-t monorepo. Any changes here MUST be mirrored in
|
|
8
|
+
* `template/package.json` so generated projects match the tracked template.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
name: 'sveltekit',
|
|
13
|
+
description: 'Minimal SvelteKit project with s-m-r-t integration',
|
|
14
|
+
framework: 'sveltekit',
|
|
15
|
+
version: '1.0.0',
|
|
16
|
+
|
|
17
|
+
// Template directory location (relative to this config file)
|
|
18
|
+
templateDir: './template',
|
|
19
|
+
|
|
20
|
+
// Dependencies to add to generated project
|
|
21
|
+
dependencies: {
|
|
22
|
+
'@happyvertical/smrt-app-runtime': '^0.53.1',
|
|
23
|
+
'@happyvertical/smrt-config': '^0.53.1',
|
|
24
|
+
'@happyvertical/smrt-core': '^0.53.1',
|
|
25
|
+
'@happyvertical/smrt-jobs': '^0.53.1',
|
|
26
|
+
'@happyvertical/smrt-profiles': '^0.53.1',
|
|
27
|
+
'@happyvertical/smrt-svelte': '^0.53.1',
|
|
28
|
+
'@happyvertical/smrt-tenancy': '^0.53.1',
|
|
29
|
+
'@happyvertical/smrt-ui': '^0.53.1',
|
|
30
|
+
'@happyvertical/smrt-users': '^0.53.1',
|
|
31
|
+
'@happyvertical/smrt-web': '^0.53.1',
|
|
32
|
+
'@happyvertical/sql': '^0.89.12',
|
|
33
|
+
'@modelcontextprotocol/server': '^2.0.0',
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
devDependencies: {
|
|
37
|
+
'@happyvertical/smrt-cli': '^0.53.1',
|
|
38
|
+
'@sveltejs/adapter-node': '^5.5.7',
|
|
39
|
+
'@sveltejs/kit': '2.70.3',
|
|
40
|
+
'@sveltejs/vite-plugin-svelte': '^7.3.0',
|
|
41
|
+
'@types/node': '^24.13.6',
|
|
42
|
+
'svelte': '^5.57.1',
|
|
43
|
+
'svelte-check': '^4.7.6',
|
|
44
|
+
'typescript': '6.0.3',
|
|
45
|
+
'vite': '8.3.0',
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// Optional: customize files
|
|
49
|
+
files: {
|
|
50
|
+
// Rename patterns during generation
|
|
51
|
+
rename: {},
|
|
52
|
+
// Skip certain files
|
|
53
|
+
skip: ['.gitkeep'],
|
|
54
|
+
// Append/merge patterns
|
|
55
|
+
append: {},
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Placeholder substitutions
|
|
59
|
+
placeholders: {
|
|
60
|
+
'{{PROJECT_NAME}}': (ctx) => ctx.name,
|
|
61
|
+
'{{PACKAGE_NAME}}': (ctx) => ctx.name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// Post-generation hooks
|
|
65
|
+
hooks: {
|
|
66
|
+
afterGenerate: async (ctx) => {
|
|
67
|
+
console.log(`\n✅ Created ${ctx.name} successfully!`);
|
|
68
|
+
console.log('\nNext steps:');
|
|
69
|
+
console.log(` cd ${ctx.name}`);
|
|
70
|
+
console.log(' pnpm install');
|
|
71
|
+
console.log(' pnpm app:install');
|
|
72
|
+
console.log();
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|