@ampless/runtime 1.0.0-beta.74 → 1.0.0-beta.78
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/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/{chunk-5BLMLMZU.js → chunk-PK7Q7YJX.js} +1 -2
- package/dist/dispatchers/index.d.ts +0 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1 -1
- package/dist/middleware.js +11 -1
- package/dist/routes/index.d.ts +19 -3
- package/dist/routes/index.js +167 -19
- package/package.json +7 -5
package/README.ja.md
CHANGED
|
@@ -121,7 +121,7 @@ CORS(匿名・読み取り専用)、64KB のボディ上限、粗い warm-in
|
|
|
121
121
|
また、`post.metadata.cache`(auto / deep / hot)+ `post.updatedAt` +
|
|
122
122
|
`cms.config.cache.{cooldownMs, freshTtlSeconds, deepTtlSeconds}` から
|
|
123
123
|
`Cache-Control` を算出してレスポンスに付与します。キャッシュ戦略の
|
|
124
|
-
契約については `
|
|
124
|
+
契約については `https://github.com/heavymoons/ampless/wiki/CONTENT` を参照してください。
|
|
125
125
|
|
|
126
126
|
## サブパス
|
|
127
127
|
|
package/README.md
CHANGED
|
@@ -119,7 +119,7 @@ returns `{ POST, OPTIONS }`; the template destructures both (a single
|
|
|
119
119
|
It also computes `Cache-Control` from `post.metadata.cache` (auto /
|
|
120
120
|
deep / hot) + `post.updatedAt` + `cms.config.cache.{cooldownMs,
|
|
121
121
|
freshTtlSeconds, deepTtlSeconds}` and sets the header on the response.
|
|
122
|
-
See `
|
|
122
|
+
See `https://github.com/heavymoons/ampless/wiki/CONTENT` for the cache strategy contract.
|
|
123
123
|
|
|
124
124
|
## Sub-paths
|
|
125
125
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/stream-s3.ts
|
|
2
2
|
import { getProperties } from "aws-amplify/storage/server";
|
|
3
|
-
import { cookies } from "next/headers";
|
|
4
3
|
var DEFAULT_THRESHOLD_BYTES = 6 * 1024 * 1024;
|
|
5
4
|
var META_CACHE_MAX = 1e3;
|
|
6
5
|
var META_CACHE_TTL_MS = 5 * 6e4;
|
|
@@ -111,7 +110,7 @@ async function streamS3Object(amplifyServerContext, key, options) {
|
|
|
111
110
|
}
|
|
112
111
|
async function streamS3ObjectWithRunner(runWithAmplifyServerContext, key, options) {
|
|
113
112
|
return runWithAmplifyServerContext({
|
|
114
|
-
nextServerContext:
|
|
113
|
+
nextServerContext: null,
|
|
115
114
|
operation: (ctx) => streamS3Object(ctx, key, options)
|
|
116
115
|
});
|
|
117
116
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export { Config, Post, ThemeManifest } from 'ampless';
|
|
|
3
3
|
import { Metadata } from 'next';
|
|
4
4
|
import { ReactNode } from 'react';
|
|
5
5
|
import { getProperties } from 'aws-amplify/storage/server';
|
|
6
|
-
import { cookies } from 'next/headers';
|
|
7
6
|
|
|
8
7
|
interface StorageOutput {
|
|
9
8
|
bucket_name: string;
|
|
@@ -625,16 +624,17 @@ declare function _resetStreamS3Cache(): void;
|
|
|
625
624
|
*/
|
|
626
625
|
declare function streamS3Object(amplifyServerContext: AmplifyServerContext, key: string, options: StreamS3Options): Promise<Response>;
|
|
627
626
|
/**
|
|
628
|
-
* Convenience wrapper
|
|
629
|
-
* adapter so route handlers don't have to duplicate the
|
|
627
|
+
* Convenience wrapper so route handlers don't have to duplicate the
|
|
630
628
|
* `runWithAmplifyServerContext` plumbing. Pass the runner from
|
|
631
629
|
* `createServerRunner({ config: outputs })` and the rest is the same
|
|
632
630
|
* shape as `streamS3Object`.
|
|
631
|
+
*
|
|
632
|
+
* Runs with `nextServerContext: null` (Amplify's stateless guest path):
|
|
633
|
+
* no cookie read or write, so the public-cacheable response can never
|
|
634
|
+
* carry a Set-Cookie.
|
|
633
635
|
*/
|
|
634
636
|
declare function streamS3ObjectWithRunner(runWithAmplifyServerContext: <T>(args: {
|
|
635
|
-
nextServerContext:
|
|
636
|
-
cookies: typeof cookies;
|
|
637
|
-
};
|
|
637
|
+
nextServerContext: null;
|
|
638
638
|
operation: (ctx: AmplifyServerContext) => Promise<T>;
|
|
639
639
|
}) => Promise<T>, key: string, options: StreamS3Options): Promise<Response>;
|
|
640
640
|
|
package/dist/index.js
CHANGED
package/dist/middleware.js
CHANGED
|
@@ -144,7 +144,13 @@ var RESERVED_PREFIXES = /* @__PURE__ */ new Set([
|
|
|
144
144
|
// internal rewrite targets — direct hits should not be middleware-driven
|
|
145
145
|
"raw",
|
|
146
146
|
"static",
|
|
147
|
-
"md"
|
|
147
|
+
"md",
|
|
148
|
+
// `/.well-known/*` is passed through to Next (RFC 8615 registry paths):
|
|
149
|
+
// the exact `/.well-known/mcp/catalog.json` is rewritten below when MCP
|
|
150
|
+
// discovery is on; everything else (e.g. a future
|
|
151
|
+
// `/.well-known/mcp-registry-auth` an operator drops in) is served by
|
|
152
|
+
// Next without a wasted AppSync flag fetch or a middleware 404.
|
|
153
|
+
".well-known"
|
|
148
154
|
]);
|
|
149
155
|
function createAmplessMiddleware(opts) {
|
|
150
156
|
return async function middleware(request) {
|
|
@@ -160,6 +166,10 @@ function createAmplessMiddleware(opts) {
|
|
|
160
166
|
}
|
|
161
167
|
requestHeaders.set(AMPLESS_PATHNAME_HEADER, url.pathname);
|
|
162
168
|
const passthrough = () => NextResponse.next({ request: { headers: requestHeaders } });
|
|
169
|
+
if (url.pathname === "/.well-known/mcp/catalog.json" && opts.cmsConfig.ai?.publicMcp === true && opts.cmsConfig.ai?.mcpDiscovery === true) {
|
|
170
|
+
url.pathname = "/api/mcp/catalog.json";
|
|
171
|
+
return NextResponse.rewrite(url, { request: { headers: requestHeaders } });
|
|
172
|
+
}
|
|
163
173
|
const segments = url.pathname.split("/").filter(Boolean);
|
|
164
174
|
if (segments.length === 0) {
|
|
165
175
|
return passthrough();
|
package/dist/routes/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import 'ampless';
|
|
|
3
3
|
import 'next';
|
|
4
4
|
import 'react';
|
|
5
5
|
import 'aws-amplify/storage/server';
|
|
6
|
-
import 'next/headers';
|
|
7
6
|
|
|
8
7
|
interface Ctx$6 {
|
|
9
8
|
params: Promise<{
|
|
@@ -55,7 +54,7 @@ type RawRouteHandler = (req: Request, ctx: Ctx$3) => Promise<Response>;
|
|
|
55
54
|
*
|
|
56
55
|
* Trust model: the body is emitted verbatim, same trust shape as
|
|
57
56
|
* regular `format: 'html'` posts on the themed route. See
|
|
58
|
-
*
|
|
57
|
+
* https://github.com/heavymoons/ampless/wiki/architecture-04-access-layer-mcp §"editor の信頼モデル".
|
|
59
58
|
*
|
|
60
59
|
* Cache-Control: deliberately omitted from the response. Middleware
|
|
61
60
|
* computes the strategy from `post.metadata.cache` + `post.updatedAt`
|
|
@@ -148,4 +147,21 @@ declare function _resetPublicMcpRateLimit(): void;
|
|
|
148
147
|
*/
|
|
149
148
|
declare function createPublicMcpRouteHandler(ampless: Ampless): PublicMcpRouteHandlers;
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
interface McpDiscoveryRouteHandlers {
|
|
151
|
+
catalog: {
|
|
152
|
+
GET: (request: Request) => Promise<Response>;
|
|
153
|
+
OPTIONS: (request: Request) => Promise<Response>;
|
|
154
|
+
};
|
|
155
|
+
serverCard: {
|
|
156
|
+
GET: (request: Request) => Promise<Response>;
|
|
157
|
+
OPTIONS: (request: Request) => Promise<Response>;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Build the discovery handlers. The template mounts the catalog handlers
|
|
162
|
+
* at `app/api/mcp/catalog.json/route.ts` (middleware rewrite target) and
|
|
163
|
+
* the server-card handlers at `app/api/mcp/server-card/route.ts`.
|
|
164
|
+
*/
|
|
165
|
+
declare function createMcpDiscoveryRouteHandlers(ampless: Ampless): McpDiscoveryRouteHandlers;
|
|
166
|
+
|
|
167
|
+
export { type FeedRouteHandler, type LlmsTxtRouteHandler, type MarkdownRouteHandler, type McpDiscoveryRouteHandlers, type OgRouteHandler, type PublicMcpRouteHandlers, type RawRouteHandler, type SitemapRouteHandler, type StaticRouteHandler, _resetPublicMcpRateLimit, createFeedRouteHandler, createLlmsTxtRouteHandler, createMarkdownRouteHandler, createMcpDiscoveryRouteHandlers, createOgRouteHandler, createPublicMcpRouteHandler, createRawRouteHandler, createSitemapRouteHandler, createStaticRouteHandler };
|
package/dist/routes/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
streamS3Object
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-PK7Q7YJX.js";
|
|
4
4
|
|
|
5
5
|
// src/routes/og.ts
|
|
6
6
|
import { ImageResponse } from "next/og";
|
|
@@ -92,7 +92,6 @@ function createRawRouteHandler(ampless) {
|
|
|
92
92
|
|
|
93
93
|
// src/routes/static.ts
|
|
94
94
|
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
|
|
95
|
-
import { cookies } from "next/headers";
|
|
96
95
|
import { getUrl } from "aws-amplify/storage/server";
|
|
97
96
|
function createStaticRouteHandler(ampless) {
|
|
98
97
|
const { runWithAmplifyServerContext } = createServerRunner({
|
|
@@ -160,7 +159,7 @@ async function serveStaticAsset({
|
|
|
160
159
|
presignedUrlFor: (k) => signStaticAsset({ runWithAmplifyServerContext, key: k })
|
|
161
160
|
};
|
|
162
161
|
return runWithAmplifyServerContext({
|
|
163
|
-
nextServerContext:
|
|
162
|
+
nextServerContext: null,
|
|
164
163
|
operation: (ctx) => streamS3Object(ctx, key, opts)
|
|
165
164
|
});
|
|
166
165
|
}
|
|
@@ -170,7 +169,7 @@ async function signStaticAsset({
|
|
|
170
169
|
}) {
|
|
171
170
|
try {
|
|
172
171
|
return await runWithAmplifyServerContext({
|
|
173
|
-
nextServerContext:
|
|
172
|
+
nextServerContext: null,
|
|
174
173
|
operation: async (amplifyContext) => {
|
|
175
174
|
const result = await getUrl(amplifyContext, {
|
|
176
175
|
path: key,
|
|
@@ -344,6 +343,42 @@ import {
|
|
|
344
343
|
MAX_BATCH
|
|
345
344
|
} from "@ampless/mcp-server/jsonrpc";
|
|
346
345
|
import { publicTools } from "@ampless/mcp-server/public";
|
|
346
|
+
|
|
347
|
+
// src/routes/_mcp-shared.ts
|
|
348
|
+
var PUBLIC_MCP_SERVER_VERSION = "0.2.0";
|
|
349
|
+
function corsHeaders(methods, extra = {}) {
|
|
350
|
+
return {
|
|
351
|
+
"Access-Control-Allow-Origin": "*",
|
|
352
|
+
"Access-Control-Allow-Methods": methods,
|
|
353
|
+
"Access-Control-Allow-Headers": "content-type, mcp-protocol-version",
|
|
354
|
+
"Access-Control-Max-Age": "86400",
|
|
355
|
+
...extra
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
var CARD_NAME_PATTERN = /^[a-zA-Z0-9.-]+\/[a-zA-Z0-9._-]+$/;
|
|
359
|
+
var CARD_NAME_MIN = 3;
|
|
360
|
+
var CARD_NAME_MAX = 200;
|
|
361
|
+
var SERVER_NAME_SUFFIX = "ampless-mcp";
|
|
362
|
+
function resolvePublicMcpIdentity(siteUrl) {
|
|
363
|
+
if (!siteUrl?.trim()) return null;
|
|
364
|
+
let hostname;
|
|
365
|
+
try {
|
|
366
|
+
const parsed = new URL(siteUrl);
|
|
367
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
368
|
+
hostname = parsed.hostname;
|
|
369
|
+
} catch {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
const labels = hostname.split(".").filter(Boolean);
|
|
373
|
+
if (labels.length === 0) return null;
|
|
374
|
+
const namespace = labels.reverse().join(".");
|
|
375
|
+
const name = `${namespace}/${SERVER_NAME_SUFFIX}`;
|
|
376
|
+
if (name.length < CARD_NAME_MIN || name.length > CARD_NAME_MAX) return null;
|
|
377
|
+
if (!CARD_NAME_PATTERN.test(name)) return null;
|
|
378
|
+
return { name, version: PUBLIC_MCP_SERVER_VERSION };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/routes/public-mcp.ts
|
|
347
382
|
var MAX_BODY_BYTES = 64 * 1024;
|
|
348
383
|
var RATE_WINDOW_MS = 6e4;
|
|
349
384
|
var RATE_MAX = 600;
|
|
@@ -363,28 +398,23 @@ function retryAfterSeconds() {
|
|
|
363
398
|
function _resetPublicMcpRateLimit() {
|
|
364
399
|
rateState = { count: 0, windowExpires: 0 };
|
|
365
400
|
}
|
|
366
|
-
function
|
|
367
|
-
return
|
|
368
|
-
"Access-Control-Allow-Origin": "*",
|
|
369
|
-
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
370
|
-
"Access-Control-Allow-Headers": "content-type, mcp-protocol-version",
|
|
371
|
-
"Access-Control-Max-Age": "86400",
|
|
372
|
-
...extra
|
|
373
|
-
};
|
|
401
|
+
function corsHeaders2(extra = {}) {
|
|
402
|
+
return corsHeaders("POST, OPTIONS", extra);
|
|
374
403
|
}
|
|
404
|
+
var STATIC_SERVER_INFO = { name: "ampless-mcp", version: "0.2" };
|
|
375
405
|
function jsonResponse(status, body) {
|
|
376
406
|
return new Response(JSON.stringify(body), {
|
|
377
407
|
status,
|
|
378
|
-
headers:
|
|
408
|
+
headers: corsHeaders2({ "Content-Type": "application/json", "Cache-Control": "no-store" })
|
|
379
409
|
});
|
|
380
410
|
}
|
|
381
411
|
function notFound() {
|
|
382
|
-
return new Response("Not Found", { status: 404, headers:
|
|
412
|
+
return new Response("Not Found", { status: 404, headers: corsHeaders2() });
|
|
383
413
|
}
|
|
384
414
|
function rateLimited() {
|
|
385
415
|
return new Response(JSON.stringify(jsonRpcError(null, -32e3, "Rate limit exceeded")), {
|
|
386
416
|
status: 429,
|
|
387
|
-
headers:
|
|
417
|
+
headers: corsHeaders2({
|
|
388
418
|
"Content-Type": "application/json",
|
|
389
419
|
"Cache-Control": "no-store",
|
|
390
420
|
"Retry-After": String(retryAfterSeconds())
|
|
@@ -396,7 +426,7 @@ function payloadTooLarge() {
|
|
|
396
426
|
JSON.stringify(jsonRpcError(null, JSON_RPC_INVALID_REQUEST, "Payload too large")),
|
|
397
427
|
{
|
|
398
428
|
status: 413,
|
|
399
|
-
headers:
|
|
429
|
+
headers: corsHeaders2({ "Content-Type": "application/json", "Cache-Control": "no-store" })
|
|
400
430
|
}
|
|
401
431
|
);
|
|
402
432
|
}
|
|
@@ -439,6 +469,11 @@ function createPublicMcpRouteHandler(ampless) {
|
|
|
439
469
|
function gateOpen() {
|
|
440
470
|
return ampless.cmsConfig.ai?.publicMcp === true;
|
|
441
471
|
}
|
|
472
|
+
async function resolveServerInfo() {
|
|
473
|
+
if (ampless.cmsConfig.ai?.mcpDiscovery !== true) return STATIC_SERVER_INFO;
|
|
474
|
+
const settings = await ampless.loadSiteSettings();
|
|
475
|
+
return resolvePublicMcpIdentity(settings.site.url) ?? STATIC_SERVER_INFO;
|
|
476
|
+
}
|
|
442
477
|
async function POST(request) {
|
|
443
478
|
try {
|
|
444
479
|
if (!gateOpen()) return notFound();
|
|
@@ -467,7 +502,7 @@ function createPublicMcpRouteHandler(ampless) {
|
|
|
467
502
|
const result = await dispatchJsonRpcMessage(parsed, {
|
|
468
503
|
tools: publicTools,
|
|
469
504
|
getContext: () => publicCtx,
|
|
470
|
-
serverInfo:
|
|
505
|
+
serverInfo: await resolveServerInfo(),
|
|
471
506
|
formatToolError,
|
|
472
507
|
maxBatch: MAX_BATCH
|
|
473
508
|
});
|
|
@@ -477,7 +512,7 @@ function createPublicMcpRouteHandler(ampless) {
|
|
|
477
512
|
if (result.status === "no-content") {
|
|
478
513
|
return new Response(null, {
|
|
479
514
|
status: 202,
|
|
480
|
-
headers:
|
|
515
|
+
headers: corsHeaders2({ "Cache-Control": "no-store" })
|
|
481
516
|
});
|
|
482
517
|
}
|
|
483
518
|
return jsonResponse(200, result.body);
|
|
@@ -488,15 +523,128 @@ function createPublicMcpRouteHandler(ampless) {
|
|
|
488
523
|
}
|
|
489
524
|
async function OPTIONS(_request) {
|
|
490
525
|
if (!gateOpen()) return notFound();
|
|
491
|
-
return new Response(null, { status: 204, headers:
|
|
526
|
+
return new Response(null, { status: 204, headers: corsHeaders2() });
|
|
492
527
|
}
|
|
493
528
|
return { POST, OPTIONS };
|
|
494
529
|
}
|
|
530
|
+
|
|
531
|
+
// src/routes/mcp-discovery.ts
|
|
532
|
+
import { resolvePublicMcpEndpoint as resolvePublicMcpEndpoint2 } from "ampless";
|
|
533
|
+
import { SUPPORTED_PROTOCOL_VERSIONS } from "@ampless/mcp-server/jsonrpc";
|
|
534
|
+
var CARD_SCHEMA_URL = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
|
|
535
|
+
var CARD_CONTENT_TYPE = "application/mcp-server-card+json";
|
|
536
|
+
var CATALOG_ENTRY_TYPE = "application/mcp-server-card+json";
|
|
537
|
+
var CARD_TEXT_MAX = 100;
|
|
538
|
+
var DESCRIPTION_TEXT = "read-only MCP endpoint for published posts (list, get, search, tags).";
|
|
539
|
+
var CONTROL_CHARS_RE2 = /[\x00-\x1f\x7f]/g;
|
|
540
|
+
function normalizeText2(s) {
|
|
541
|
+
return s.replace(CONTROL_CHARS_RE2, " ").replace(/\s+/g, " ").trim();
|
|
542
|
+
}
|
|
543
|
+
function truncate(s, max) {
|
|
544
|
+
const codePoints = [...s];
|
|
545
|
+
return codePoints.length > max ? codePoints.slice(0, max).join("") : s;
|
|
546
|
+
}
|
|
547
|
+
function discoveryEnabled(ampless) {
|
|
548
|
+
return ampless.cmsConfig.ai?.publicMcp === true && ampless.cmsConfig.ai?.mcpDiscovery === true;
|
|
549
|
+
}
|
|
550
|
+
async function resolveContext(ampless) {
|
|
551
|
+
const settings = await ampless.loadSiteSettings();
|
|
552
|
+
const endpoint = resolvePublicMcpEndpoint2(true, settings.site.url);
|
|
553
|
+
if (typeof endpoint !== "string") return null;
|
|
554
|
+
const identity = resolvePublicMcpIdentity(settings.site.url);
|
|
555
|
+
if (!identity) return null;
|
|
556
|
+
const url = new URL(endpoint);
|
|
557
|
+
const { origin, hostname } = url;
|
|
558
|
+
return {
|
|
559
|
+
origin,
|
|
560
|
+
hostname,
|
|
561
|
+
endpoint: `${origin}/api/mcp`,
|
|
562
|
+
identity,
|
|
563
|
+
siteName: settings.site.name ?? ""
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
function notFound2() {
|
|
567
|
+
return new Response("Not Found", {
|
|
568
|
+
status: 404,
|
|
569
|
+
headers: corsHeaders("GET, OPTIONS")
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
function jsonResponse2(body, contentType) {
|
|
573
|
+
return new Response(JSON.stringify(body), {
|
|
574
|
+
status: 200,
|
|
575
|
+
headers: corsHeaders("GET, OPTIONS", {
|
|
576
|
+
"Content-Type": contentType,
|
|
577
|
+
// Spec SHOULD value — discovery documents are cheap and stable.
|
|
578
|
+
"Cache-Control": "public, max-age=3600"
|
|
579
|
+
})
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
function createMcpDiscoveryRouteHandlers(ampless) {
|
|
583
|
+
async function catalogGET(_request) {
|
|
584
|
+
if (!discoveryEnabled(ampless)) return notFound2();
|
|
585
|
+
const ctx = await resolveContext(ampless);
|
|
586
|
+
if (!ctx) {
|
|
587
|
+
console.warn(
|
|
588
|
+
"[ampless] MCP discovery catalog: site.url is unset or unresolvable \u2014 skipping (404)"
|
|
589
|
+
);
|
|
590
|
+
return notFound2();
|
|
591
|
+
}
|
|
592
|
+
const body = {
|
|
593
|
+
specVersion: "draft",
|
|
594
|
+
entries: [
|
|
595
|
+
{
|
|
596
|
+
// URN tail matches the Card name's `/`-suffix (`ampless-mcp`).
|
|
597
|
+
identifier: `urn:air:${ctx.hostname}:ampless-mcp`,
|
|
598
|
+
type: CATALOG_ENTRY_TYPE,
|
|
599
|
+
url: `${ctx.origin}/api/mcp/server-card`
|
|
600
|
+
}
|
|
601
|
+
]
|
|
602
|
+
};
|
|
603
|
+
return jsonResponse2(body, "application/json");
|
|
604
|
+
}
|
|
605
|
+
async function serverCardGET(_request) {
|
|
606
|
+
if (!discoveryEnabled(ampless)) return notFound2();
|
|
607
|
+
const ctx = await resolveContext(ampless);
|
|
608
|
+
if (!ctx) {
|
|
609
|
+
console.warn(
|
|
610
|
+
"[ampless] MCP discovery server-card: site.url is unset or unresolvable \u2014 skipping (404)"
|
|
611
|
+
);
|
|
612
|
+
return notFound2();
|
|
613
|
+
}
|
|
614
|
+
const normalizedName = normalizeText2(ctx.siteName);
|
|
615
|
+
const rawDescription = normalizedName ? `${normalizedName} \u2014 ${DESCRIPTION_TEXT}` : DESCRIPTION_TEXT;
|
|
616
|
+
const card = {
|
|
617
|
+
$schema: CARD_SCHEMA_URL,
|
|
618
|
+
name: ctx.identity.name,
|
|
619
|
+
version: ctx.identity.version,
|
|
620
|
+
description: truncate(rawDescription, CARD_TEXT_MAX)
|
|
621
|
+
};
|
|
622
|
+
if (normalizedName) card.title = truncate(normalizedName, CARD_TEXT_MAX);
|
|
623
|
+
card.websiteUrl = ctx.origin;
|
|
624
|
+
card.remotes = [
|
|
625
|
+
{
|
|
626
|
+
type: "streamable-http",
|
|
627
|
+
url: ctx.endpoint,
|
|
628
|
+
supportedProtocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS]
|
|
629
|
+
}
|
|
630
|
+
];
|
|
631
|
+
return jsonResponse2(card, CARD_CONTENT_TYPE);
|
|
632
|
+
}
|
|
633
|
+
async function optionsHandler(_request) {
|
|
634
|
+
if (!discoveryEnabled(ampless)) return notFound2();
|
|
635
|
+
return new Response(null, { status: 204, headers: corsHeaders("GET, OPTIONS") });
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
catalog: { GET: catalogGET, OPTIONS: optionsHandler },
|
|
639
|
+
serverCard: { GET: serverCardGET, OPTIONS: optionsHandler }
|
|
640
|
+
};
|
|
641
|
+
}
|
|
495
642
|
export {
|
|
496
643
|
_resetPublicMcpRateLimit,
|
|
497
644
|
createFeedRouteHandler,
|
|
498
645
|
createLlmsTxtRouteHandler,
|
|
499
646
|
createMarkdownRouteHandler,
|
|
647
|
+
createMcpDiscoveryRouteHandlers,
|
|
500
648
|
createOgRouteHandler,
|
|
501
649
|
createPublicMcpRouteHandler,
|
|
502
650
|
createRawRouteHandler,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/runtime",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.78",
|
|
4
4
|
"description": "Public-side runtime for ampless: post fetching, theme dispatch, middleware, public route handlers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"htmlparser2": "^10.1.0",
|
|
50
50
|
"lucide-react": "^1.16.0",
|
|
51
51
|
"marked": "^18.0.4",
|
|
52
|
-
"sanitize-html": "^2.
|
|
52
|
+
"sanitize-html": "^2.18.1",
|
|
53
53
|
"tailwind-merge": "^3.6.0",
|
|
54
|
-
"@ampless/mcp-server": "1.0.0-beta.
|
|
55
|
-
"ampless": "
|
|
56
|
-
"
|
|
54
|
+
"@ampless/mcp-server": "1.0.0-beta.70",
|
|
55
|
+
"@ampless/plugin-og-image": "0.2.0-beta.63",
|
|
56
|
+
"ampless": "1.0.0-beta.63"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@aws-amplify/adapter-nextjs": "^1",
|
|
@@ -66,6 +66,8 @@
|
|
|
66
66
|
"@types/react": "^19.2.15",
|
|
67
67
|
"@types/react-dom": "^19.2.3",
|
|
68
68
|
"@types/sanitize-html": "^2.16.1",
|
|
69
|
+
"ajv": "^8.17.1",
|
|
70
|
+
"ajv-formats": "^3.0.1",
|
|
69
71
|
"next": "^16.2.6",
|
|
70
72
|
"react": "^19.2.6",
|
|
71
73
|
"react-dom": "^19.2.6"
|