diff --git a/docs/api-reference/mcp-server.mdx b/docs/api-reference/mcp-server.mdx
index 2909a211d..836241b99 100644
--- a/docs/api-reference/mcp-server.mdx
+++ b/docs/api-reference/mcp-server.mdx
@@ -68,6 +68,22 @@ server.registerTool(config, handler): this;
Registers a tool, optionally bound to a view. See [registerTool](/api-reference/register-tool) for the full config and handler.
+### `registerResource`
+
+```ts
+server.registerResource(config, handler): this;
+```
+
+Registers a [resource](/build/resources) the host can read by URI, static or template-based. See [registerResource](/api-reference/register-resource).
+
+### `registerPrompt`
+
+```ts
+server.registerPrompt(config, handler): this;
+```
+
+Registers a [prompt](/build/prompts) the user can invoke from the host's prompt menu. See [registerPrompt](/api-reference/register-prompt).
+
### `use`
```ts
diff --git a/docs/api-reference/register-prompt.mdx b/docs/api-reference/register-prompt.mdx
new file mode 100644
index 000000000..26ff45c0f
--- /dev/null
+++ b/docs/api-reference/register-prompt.mdx
@@ -0,0 +1,90 @@
+---
+title: registerPrompt
+description: "Ship a reusable prompt users can invoke"
+---
+
+`registerPrompt` adds a [prompt](/build/prompts) to your server: a named, parameterized message template the user picks from the host's prompt menu. The handler returns the messages the host inserts into the conversation.
+
+## Example
+
+A prompt that takes one argument and returns a single user message.
+
+```ts server.ts
+import { McpServer } from "skybridge/server";
+import { z } from "zod";
+
+const server = new McpServer({ name: "travel", version: "1.0" }).registerPrompt(
+ {
+ name: "trip-summary",
+ title: "Trip summary",
+ description: "Summarize a trip to a destination.",
+ argsSchema: { destination: z.string() },
+ },
+ ({ destination }) => ({
+ messages: [
+ {
+ role: "user",
+ content: { type: "text", text: `Summarize a trip to ${destination}.` },
+ },
+ ],
+ }),
+);
+```
+
+## Signature
+
+```ts
+server.registerPrompt(config: PromptConfig, handler: PromptHandler): McpServer;
+```
+
+The config-object form returns the server, so it chains alongside [`registerTool`](/api-reference/register-tool) and [`registerResource`](/api-reference/register-resource).
+
+## Config
+
+```ts
+type PromptConfig = {
+ name: string;
+ title?: string;
+ description?: string;
+ argsSchema?: ZodRawShape; // Zod shape; validates args and types the handler
+};
+```
+
+### `name`, `title`, `description`
+
+`name` identifies the prompt; `title` and `description` are what the host shows in its prompt menu.
+
+### `argsSchema`
+
+A [Zod](https://zod.dev/) raw shape whose fields become the prompt's arguments. It validates what the host passes and types the handler's input. Wrap a field in `completable()` to autocomplete it as the user types, see [Completions](/build/prompts#completions).
+
+## Handler
+
+Receives the validated arguments and returns the messages to insert.
+
+```ts
+type PromptHandler = (
+ args: Args,
+ extra: RequestHandlerExtra,
+) => Promise<{
+ description?: string;
+ messages: Array<{
+ role: "user" | "assistant";
+ content: ContentBlock;
+ }>;
+}>;
+```
+
+Each message's `content` is a standard MCP [`ContentBlock`](/api-reference/register-tool#content-helpers), most often `{ type: "text", text }`.
+
+
+
+ The guide, with completions
+
+
+ Expose readable content
+
+
+ The server you register on
+
+
diff --git a/docs/api-reference/register-resource.mdx b/docs/api-reference/register-resource.mdx
new file mode 100644
index 000000000..94c02ce68
--- /dev/null
+++ b/docs/api-reference/register-resource.mdx
@@ -0,0 +1,135 @@
+---
+title: registerResource
+description: "Expose a resource the host can read"
+---
+
+`registerResource` adds a [resource](/build/resources) to your server: addressable content a host can read by URI, such as reference docs, a data file, or a live-generated report. Pass a `uri` for a fixed resource or a `template` for a family of URIs.
+
+## Example
+
+A static Markdown document at a fixed URI.
+
+```ts server.ts
+import { McpServer } from "skybridge/server";
+
+const server = new McpServer({ name: "shop", version: "1.0" }).registerResource(
+ {
+ name: "pricing",
+ uri: "docs://pricing",
+ title: "Pricing",
+ description: "Current plan pricing, in Markdown.",
+ mimeType: "text/markdown",
+ },
+ async (uri) => ({
+ contents: [{ uri: uri.href, text: await loadPricingDoc() }],
+ }),
+);
+```
+
+## Signature
+
+```ts
+server.registerResource(config: ResourceConfig, handler: ReadHandler): McpServer;
+```
+
+The config-object form returns the server, so it chains alongside [`registerTool`](/api-reference/register-tool) and [`registerPrompt`](/api-reference/register-prompt).
+
+## Config
+
+Provide either `uri` (static) or `template` (dynamic), never both. The remaining fields are the resource's metadata.
+
+```ts
+type ResourceConfig =
+ | { name: string; uri: string } & ResourceMetadata
+ | { name: string; template: ResourceTemplate } & ResourceMetadata;
+
+type ResourceMetadata = {
+ title?: string;
+ description?: string;
+ mimeType?: string;
+ _meta?: Record;
+};
+```
+
+### `name`
+
+Identifier for the resource, unique per server.
+
+### `uri`
+
+The fixed URI a static resource is read from, e.g. `docs://pricing`. The handler receives this back as a `URL`.
+
+
+The `ui://views/` namespace is reserved for Skybridge view resources. Registering a resource under it throws.
+
+
+### `template`
+
+A [`ResourceTemplate`](https://github.com/modelcontextprotocol/typescript-sdk) for a family of URIs, described by an [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570) URI template. Use it when the URI carries a variable, such as a record id.
+
+```ts
+import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
+
+server.registerResource(
+ {
+ name: "order",
+ template: new ResourceTemplate("orders://{orderId}", {
+ // List concrete resources the template can produce (or `undefined`).
+ list: async () => ({
+ resources: (await recentOrders()).map((id) => ({
+ name: id,
+ uri: `orders://${id}`,
+ })),
+ }),
+ // Autocomplete a template variable as the user types.
+ complete: {
+ orderId: async (value) => (await matchOrderIds(value)).slice(0, 20),
+ },
+ }),
+ },
+ async (uri, { orderId }) => ({
+ contents: [{ uri: uri.href, text: await renderOrder(orderId as string) }],
+ }),
+);
+```
+
+The `list` callback is required (pass `undefined` to opt out) so a resource family is never accidentally unlistable. See [Completions](/build/resources#completions).
+
+### `mimeType`, `title`, `description`, `_meta`
+
+`mimeType` labels the content (`text/markdown`, `application/json`, …). `title` and `description` are the discovery surface a host shows. `_meta` carries free-form metadata, forwarded untouched.
+
+## Handler
+
+Runs when the resource is read. A static resource's handler receives the requested `uri`; a template's also receives the resolved template `variables`.
+
+```ts
+type ReadHandler = (
+ uri: URL,
+ variablesOrExtra: Variables | RequestHandlerExtra,
+ extra?: RequestHandlerExtra,
+) => Promise<{
+ contents: Array<{
+ uri: string;
+ mimeType?: string;
+ text?: string; // text content
+ blob?: string; // base64 content
+ _meta?: Record;
+ }>;
+ _meta?: Record;
+}>;
+```
+
+Return `text` for text resources or `blob` (base64) for binary. Set each entry's `uri` to `uri.href` so the response echoes the URI that was requested.
+
+
+
+ The guide, with completions
+
+
+ Ship reusable prompts
+
+
+ The server you register on
+
+
diff --git a/docs/build/prompts.mdx b/docs/build/prompts.mdx
new file mode 100644
index 000000000..7004ebb78
--- /dev/null
+++ b/docs/build/prompts.mdx
@@ -0,0 +1,78 @@
+---
+title: "Register Prompts"
+description: "Ship reusable prompts users can invoke"
+icon: "message-square"
+---
+
+A **prompt** is the third MCP server primitive, alongside [tools](/build/tools) and [resources](/build/resources). It's a named, parameterized message template the user picks from the host's prompt menu; your handler returns the messages the host drops into the conversation. Use it to ship canned starting points, "Summarize this trip", "Draft a reply", so users don't retype them.
+
+```ts server.ts
+import { McpServer } from "skybridge/server";
+import { z } from "zod";
+
+const server = new McpServer({ name: "travel", version: "1.0" }).registerPrompt(
+ {
+ name: "trip-summary",
+ title: "Trip summary",
+ description: "Summarize a trip to a destination.",
+ argsSchema: { destination: z.string() },
+ },
+ ({ destination }) => ({
+ messages: [
+ {
+ role: "user",
+ content: { type: "text", text: `Summarize a trip to ${destination}.` },
+ },
+ ],
+ }),
+);
+```
+
+[`registerPrompt`](/api-reference/register-prompt) returns the server, so it chains next to your other registrations. `argsSchema` is a [Zod](https://zod.dev/) shape: it validates the arguments the host passes and types the handler's input.
+
+
+Register prompts up front, at startup. Adding or removing them at runtime relies on `list_changed` notifications, which the stateless JSON transport can't deliver, so hosts won't see the change.
+
+
+## Completions
+
+Wrap an argument in `completable()` to autocomplete it as the user fills in the prompt:
+
+```ts server.ts
+import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
+import { z } from "zod";
+
+server.registerPrompt(
+ {
+ name: "trip-summary",
+ argsSchema: {
+ destination: completable(z.string(), async (value) =>
+ (await matchCities(value)).slice(0, 20),
+ ),
+ },
+ },
+ ({ destination }) => ({
+ messages: [
+ {
+ role: "user",
+ content: { type: "text", text: `Summarize a trip to ${destination}.` },
+ },
+ ],
+ }),
+);
+```
+
+The completion callback receives what the user has typed and returns the candidate values.
+
+## Go Further
+
+
+ Full config and handler reference
+
+
+ Expose readable content
+
+
+ The action primitive
+
+
diff --git a/docs/build/resources.mdx b/docs/build/resources.mdx
new file mode 100644
index 000000000..0f0779d7c
--- /dev/null
+++ b/docs/build/resources.mdx
@@ -0,0 +1,93 @@
+---
+title: "Register Resources"
+description: "Expose readable content to the host"
+icon: "file-text"
+---
+
+MCP has three server primitives. [Tools](/build/tools) are the ones the model calls; resources and prompts round out the set. A **resource** is addressable content a host can read by URI: reference docs, a data file, a live-generated report. Where a tool is an action, a resource is a document.
+
+```ts server.ts
+import { McpServer } from "skybridge/server";
+
+const server = new McpServer({ name: "shop", version: "1.0" }).registerResource(
+ {
+ name: "pricing",
+ uri: "docs://pricing",
+ title: "Pricing",
+ description: "Current plan pricing, in Markdown.",
+ mimeType: "text/markdown",
+ },
+ async (uri) => ({
+ contents: [{ uri: uri.href, text: await loadPricingDoc() }],
+ }),
+);
+```
+
+[`registerResource`](/api-reference/register-resource) returns the server, so it chains next to your tool registrations. The handler runs when the host reads the URI and returns the content: `text` for text, `blob` (base64) for binary.
+
+
+Register resources up front, at startup. Adding or removing them at runtime relies on `list_changed` notifications, which the stateless JSON transport can't deliver, so hosts won't see the change.
+
+
+## Static vs. dynamic
+
+A **static** resource lives at one fixed `uri`. A **dynamic** resource uses a `template`, an [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570) URI template, to cover a family of URIs that share a shape:
+
+```ts server.ts
+import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
+
+server.registerResource(
+ {
+ name: "order",
+ template: new ResourceTemplate("orders://{orderId}", { list: undefined }),
+ },
+ async (uri, { orderId }) => ({
+ contents: [{ uri: uri.href, text: await renderOrder(orderId as string) }],
+ }),
+);
+```
+
+The template's handler receives the resolved `variables` as its second argument.
+
+
+The `ui://views/` namespace is reserved for the view resources Skybridge registers for you. Registering a resource under it throws.
+
+
+## Completions
+
+A dynamic resource can help the host discover and complete its URIs. The `ResourceTemplate` constructor takes two callbacks:
+
+```ts server.ts
+new ResourceTemplate("orders://{orderId}", {
+ // Enumerate concrete resources the template produces.
+ list: async () => ({
+ resources: (await recentOrders()).map((id) => ({
+ name: id,
+ uri: `orders://${id}`,
+ })),
+ }),
+ // Autocomplete a variable as the user types it.
+ complete: {
+ orderId: async (value) => (await matchOrderIds(value)).slice(0, 20),
+ },
+});
+```
+
+`list` is required: pass `undefined` to opt out, so a template is never accidentally unlistable. `complete` is optional, one callback per template variable, returning the candidate values for what the user has typed so far.
+
+
+ The third primitive: reusable prompts users can invoke.
+
+
+## Go Further
+
+
+ Full config and handler reference
+
+
+ Ship reusable prompts
+
+
+ The action primitive
+
+
diff --git a/docs/docs.json b/docs/docs.json
index 303efbf86..140b85a8b 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -38,6 +38,8 @@
"root": "build/index",
"pages": [
"build/tools",
+ "build/resources",
+ "build/prompts",
"build/view",
"build/state",
"build/auth"
@@ -99,7 +101,9 @@
"icon": "server",
"pages": [
"api-reference/mcp-server",
- "api-reference/register-tool"
+ "api-reference/register-tool",
+ "api-reference/register-resource",
+ "api-reference/register-prompt"
]
},
{
diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts
index 6820d6f2e..3724f2156 100644
--- a/packages/core/src/server/index.ts
+++ b/packages/core/src/server/index.ts
@@ -44,6 +44,8 @@ export type {
JsonOptions,
KnownToolMeta,
McpServerTypes,
+ PromptConfig,
+ ResourceConfig,
SecurityScheme,
SkybridgeServerOptions,
ToolDef,
diff --git a/packages/core/src/server/register-resource-prompt.test-d.ts b/packages/core/src/server/register-resource-prompt.test-d.ts
new file mode 100644
index 000000000..66259dac5
--- /dev/null
+++ b/packages/core/src/server/register-resource-prompt.test-d.ts
@@ -0,0 +1,40 @@
+import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
+import { expectTypeOf, test } from "vitest";
+import { z } from "zod";
+import type { McpServer } from "./server.js";
+
+const server = null as unknown as McpServer;
+
+test("registerResource config object is chainable", () => {
+ expectTypeOf(
+ server.registerResource(
+ { name: "pricing", uri: "docs://pricing", mimeType: "text/markdown" },
+ async (uri) => ({ contents: [{ uri: uri.href, text: "" }] }),
+ ),
+ ).toEqualTypeOf();
+});
+
+test("registerResource template variant passes variables to the callback", () => {
+ server.registerResource(
+ {
+ name: "doc",
+ template: new ResourceTemplate("docs://{id}", { list: undefined }),
+ },
+ async (uri, variables) => {
+ expectTypeOf(variables).toBeObject();
+ return { contents: [{ uri: uri.href, text: "" }] };
+ },
+ );
+});
+
+test("registerPrompt config object is chainable and typed args", () => {
+ expectTypeOf(
+ server.registerPrompt(
+ { name: "trip-summary", argsSchema: { destination: z.string() } },
+ ({ destination }) => {
+ expectTypeOf(destination).toEqualTypeOf();
+ return { messages: [] };
+ },
+ ),
+ ).toEqualTypeOf();
+});
diff --git a/packages/core/src/server/register-resource-prompt.test.ts b/packages/core/src/server/register-resource-prompt.test.ts
new file mode 100644
index 000000000..cef313de1
--- /dev/null
+++ b/packages/core/src/server/register-resource-prompt.test.ts
@@ -0,0 +1,69 @@
+import { Client } from "@modelcontextprotocol/sdk/client/index.js";
+import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
+import { describe, expect, it } from "vitest";
+import { z } from "zod";
+import { McpServer } from "./index.js";
+
+async function connect(register: (server: McpServer) => void) {
+ const server = new McpServer({ name: "test", version: "1.0.0" });
+ register(server);
+ const client = new Client({ name: "test-client", version: "1.0.0" });
+ const [clientTransport, serverTransport] =
+ InMemoryTransport.createLinkedPair();
+ await server.connect(serverTransport);
+ await client.connect(clientTransport);
+ return {
+ client,
+ teardown: async () => {
+ await client.close();
+ await server.close();
+ },
+ };
+}
+
+describe("registerResource / registerPrompt config-object API", () => {
+ it("registers and reads a resource, chaining with registerPrompt", async () => {
+ const { client, teardown } = await connect((server) => {
+ const chained = server
+ .registerResource(
+ { name: "pricing", uri: "docs://pricing", mimeType: "text/markdown" },
+ async (uri) => ({ contents: [{ uri: uri.href, text: "$5" }] }),
+ )
+ .registerPrompt(
+ { name: "trip", argsSchema: { destination: z.string() } },
+ ({ destination }) => ({
+ messages: [
+ {
+ role: "user",
+ content: { type: "text", text: `Trip to ${destination}` },
+ },
+ ],
+ }),
+ );
+ expect(chained).toBe(server);
+ });
+
+ const { contents } = await client.readResource({ uri: "docs://pricing" });
+ expect((contents[0] as { text: string }).text).toBe("$5");
+
+ const prompt = await client.getPrompt({
+ name: "trip",
+ arguments: { destination: "Rome" },
+ });
+ expect((prompt.messages[0]?.content as { text: string }).text).toBe(
+ "Trip to Rome",
+ );
+
+ await teardown();
+ });
+
+ it("rejects resources colliding with the reserved ui://views/ namespace", () => {
+ const server = new McpServer({ name: "test", version: "1.0.0" });
+ expect(() =>
+ server.registerResource(
+ { name: "sneaky", uri: "ui://views/ext-apps/x.html" },
+ async (uri) => ({ contents: [{ uri: uri.href, text: "" }] }),
+ ),
+ ).toThrow(/reserved/);
+ });
+});
diff --git a/packages/core/src/server/server.ts b/packages/core/src/server/server.ts
index fe5b3f2fc..7289f21e1 100644
--- a/packages/core/src/server/server.ts
+++ b/packages/core/src/server/server.ts
@@ -10,7 +10,17 @@ import {
Server as SdkServer,
type ServerOptions,
} from "@modelcontextprotocol/sdk/server/index.js";
-import { McpServer as McpServerBase } from "@modelcontextprotocol/sdk/server/mcp.js";
+import {
+ McpServer as McpServerBase,
+ type PromptCallback,
+ type ReadResourceCallback,
+ type ReadResourceTemplateCallback,
+ type RegisteredPrompt,
+ type RegisteredResource,
+ type RegisteredResourceTemplate,
+ type ResourceMetadata,
+ type ResourceTemplate,
+} from "@modelcontextprotocol/sdk/server/mcp.js";
import type {
AnySchema,
SchemaOutput,
@@ -344,6 +354,33 @@ interface ToolConfig {
_meta?: ToolMeta;
}
+/**
+ * Config object for {@link McpServer.registerResource}. Provide `uri` for a
+ * static resource or `template` (a `ResourceTemplate`) for a dynamic one; the
+ * remaining fields mirror the SDK's `ResourceMetadata` (`title`, `description`,
+ * `mimeType`, `_meta`, …).
+ */
+export type ResourceConfig =
+ | ({ name: string; uri: string; template?: never } & ResourceMetadata)
+ | ({
+ name: string;
+ template: ResourceTemplate;
+ uri?: never;
+ } & ResourceMetadata);
+
+/**
+ * Config object for {@link McpServer.registerPrompt}. `argsSchema` is a Zod raw
+ * shape; wrap individual args in `completable()` for argument autocompletion.
+ */
+export interface PromptConfig<
+ Args extends ZodRawShapeCompat = ZodRawShapeCompat,
+> {
+ name: string;
+ title?: string;
+ description?: string;
+ argsSchema?: Args;
+}
+
/**
* Optional client-supplied hints attached to `params._meta` on every tool call
* by the Apps SDK host. Hints only: never use for authorization, and tolerate
@@ -404,6 +441,15 @@ function stripQuery(uri: string): string {
return queryIndex === -1 ? uri : uri.slice(0, queryIndex);
}
+/** `ui://views/` is reserved for Skybridge's internal view HTML resources. */
+function assertNotViewNamespace(uri: string): void {
+ if (uri.startsWith("ui://views/")) {
+ throw new Error(
+ `Cannot register resource "${uri}": the "ui://views/" namespace is reserved for Skybridge views.`,
+ );
+ }
+}
+
/**
* Coerce a tool handler's return value into an MCP `content` array. Strings
* become a single `TextContent`; a single block is wrapped in an array;
@@ -425,11 +471,15 @@ export function normalizeContent(
return [content];
}
-// We Omit `registerTool` from the base class at the type level so our
-// unified 2-arg signature can replace the SDK's 3-arg one without an
-// incompatible override. The runtime prototype chain is unaffected.
+// We Omit `registerTool`/`registerResource`/`registerPrompt` from the base
+// class at the type level so our config-object overloads can sit alongside the
+// SDK's positional ones without an incompatible override. The runtime prototype
+// chain is unaffected.
interface McpServerBaseOmitted
- extends Omit {}
+ extends Omit<
+ McpServerBase,
+ "registerTool" | "registerResource" | "registerPrompt" | "connect"
+ > {}
const McpServerBaseOmitted = McpServerBase as unknown as new (
...args: ConstructorParameters
) => McpServerBaseOmitted;
@@ -1297,4 +1347,95 @@ export class McpServer<
return this;
}
+
+ /**
+ * Register an MCP resource. Pass a config object for a chainable,
+ * Skybridge-style registration; the SDK's positional overloads remain
+ * available for internal use.
+ *
+ * @example
+ * ```ts
+ * server.registerResource(
+ * { name: "pricing", uri: "docs://pricing", mimeType: "text/markdown" },
+ * async (uri) => ({ contents: [{ uri: uri.href, text: pricingDoc }] }),
+ * );
+ * ```
+ *
+ * @see https://docs.skybridge.tech/build/resources
+ */
+ registerResource(
+ config: { name: string; uri: string } & ResourceMetadata,
+ readCallback: ReadResourceCallback,
+ ): this;
+ registerResource(
+ config: { name: string; template: ResourceTemplate } & ResourceMetadata,
+ readCallback: ReadResourceTemplateCallback,
+ ): this;
+ registerResource(
+ name: string,
+ uri: string,
+ config: ResourceMetadata,
+ readCallback: ReadResourceCallback,
+ ): RegisteredResource;
+ registerResource(
+ name: string,
+ template: ResourceTemplate,
+ config: ResourceMetadata,
+ readCallback: ReadResourceTemplateCallback,
+ ): RegisteredResourceTemplate;
+ registerResource(...args: unknown[]): unknown {
+ const baseFn = McpServerBase.prototype.registerResource as (
+ ...args: unknown[]
+ ) => unknown;
+
+ if (typeof args[0] === "string") {
+ return baseFn.call(this, args[0], args[1], args[2], args[3]);
+ }
+
+ const { name, uri, template, ...metadata } = args[0] as ResourceConfig;
+ const uriOrTemplate = template ?? uri;
+ assertNotViewNamespace(
+ template ? String(template.uriTemplate) : (uri as string),
+ );
+ baseFn.call(this, name, uriOrTemplate, metadata, args[1]);
+ return this;
+ }
+
+ /**
+ * Register an MCP prompt. Pass a config object for a chainable,
+ * Skybridge-style registration; the SDK's positional overloads remain
+ * available for internal use.
+ *
+ * @example
+ * ```ts
+ * server.registerPrompt(
+ * { name: "trip-summary", argsSchema: { destination: z.string() } },
+ * ({ destination }) => ({ messages: [] }),
+ * );
+ * ```
+ *
+ * @see https://docs.skybridge.tech/build/prompts
+ */
+ registerPrompt(
+ config: PromptConfig,
+ cb: PromptCallback,
+ ): this;
+ registerPrompt(
+ name: string,
+ config: { title?: string; description?: string; argsSchema?: Args },
+ cb: PromptCallback,
+ ): RegisteredPrompt;
+ registerPrompt(...args: unknown[]): unknown {
+ const baseFn = McpServerBase.prototype.registerPrompt as (
+ ...args: unknown[]
+ ) => unknown;
+
+ if (typeof args[0] === "string") {
+ return baseFn.call(this, args[0], args[1], args[2]);
+ }
+
+ const { name, ...config } = args[0] as PromptConfig;
+ baseFn.call(this, name, config, args[1]);
+ return this;
+ }
}
diff --git a/skills/chatgpt-app-builder/references/architecture.md b/skills/chatgpt-app-builder/references/architecture.md
index d09368e9c..3af6c9b14 100644
--- a/skills/chatgpt-app-builder/references/architecture.md
+++ b/skills/chatgpt-app-builder/references/architecture.md
@@ -9,6 +9,8 @@ A **view** is a tool with a UI. It renders the tool output visually. The UI is a
- manage its own state
- call other tools to fetch data absent from the view output schema or trigger actions.
+A **resource** is readable content addressed by URI (`server.registerResource`), static or template-based; a **prompt** is a reusable message template the user invokes (`server.registerPrompt`). Both chain off the server like `registerTool`. Reach for them only when the SPEC calls for exposing reference content or canned prompts; most apps need only tools and views. See `/build/resources` and `/build/prompts`.
+
## Step 1: Identify the UX Flows
A **flow** is an end-to-end user journey that accomplishes one goal (e.g., "book a flight" = search → select → checkout).