@solvapay/mcp 0.1.0

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 SolvaPay Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @solvapay/mcp
2
+
3
+ Official `@modelcontextprotocol/sdk` + `@modelcontextprotocol/ext-apps`
4
+ adapter for the SolvaPay MCP toolbox.
5
+
6
+ This is the only SolvaPay package that imports `@modelcontextprotocol/*`.
7
+ Framework-neutral contracts (tool names, descriptors, paywall meta,
8
+ OAuth discovery JSON, JWT helpers) live in
9
+ [`@solvapay/mcp-core`](../mcp-core) so alternative adapters
10
+ (`fastmcp`, raw JSON-RPC) can reuse the same contract. Runtime-specific
11
+ OAuth middleware lives alongside:
12
+
13
+ - [`@solvapay/mcp-express`](../mcp-express) — Node `(req, res, next)`.
14
+ - [`@solvapay/mcp-fetch`](../mcp-fetch) — Web standards `(req: Request) => Promise<Response>`.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pnpm add @solvapay/mcp @solvapay/server \
20
+ @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps zod
21
+ ```
22
+
23
+ ## Importing
24
+
25
+ Everything you need for a paywalled tool lives in this package:
26
+
27
+ ```ts
28
+ import {
29
+ createSolvaPayMcpServer,
30
+ registerPayableTool,
31
+ type ResponseContext,
32
+ type NudgeSpec,
33
+ } from '@solvapay/mcp'
34
+ ```
35
+
36
+ Reach into `@solvapay/mcp-core` directly only if you're writing a
37
+ framework adapter (`fastmcp`, raw JSON-RPC).
38
+
39
+ ## Quick start
40
+
41
+ ```ts
42
+ import { createSolvaPayMcpServer } from '@solvapay/mcp'
43
+ import { createSolvaPay } from '@solvapay/server'
44
+ import { z } from 'zod'
45
+
46
+ const solvaPay = createSolvaPay({ apiKey: process.env.SOLVAPAY_SECRET_KEY! })
47
+
48
+ const server = createSolvaPayMcpServer({
49
+ solvaPay,
50
+ productRef: 'prd_video',
51
+ resourceUri: 'ui://my-app/mcp-app.html',
52
+ htmlPath: './dist/mcp-app.html',
53
+ publicBaseUrl: 'https://my-app.example.com',
54
+ additionalTools: ({ registerPayable }) => {
55
+ registerPayable('create_video', {
56
+ schema: { prompt: z.string() },
57
+ description: 'Generate a short video from a text prompt.',
58
+ handler: async ({ prompt }, ctx) => {
59
+ const videoUrl = await generateVideo(prompt)
60
+ return ctx.respond({ videoUrl })
61
+ },
62
+ })
63
+ },
64
+ })
65
+ ```
66
+
67
+ One call wires the full SolvaPay transport surface (`check_purchase`,
68
+ `create_payment_intent`, `process_payment`, `open_checkout`, etc.), the
69
+ UI resource with the Stripe CSP baseline, and any integrator-defined
70
+ tools via `additionalTools`.
71
+
72
+ ## Handler contract
73
+
74
+ `registerPayable` handlers receive parsed `args` (inferred from
75
+ `schema` when provided) and a `ResponseContext`. They must return the
76
+ branded envelope produced by `ctx.respond(data, options?)`.
77
+
78
+ ```ts
79
+ handler: async ({ prompt }, ctx) => {
80
+ const video = await generate(prompt)
81
+ // Attach an upsell nudge when the customer is low on credits.
82
+ if (ctx.customer.balance < 500) {
83
+ return ctx.respond({ videoUrl: video.url }, {
84
+ nudge: { kind: 'low-balance', message: 'Running low on credits' },
85
+ })
86
+ }
87
+ return ctx.respond({ videoUrl: video.url })
88
+ }
89
+ ```
90
+
91
+ The [`ctx.respond()` V1 spec](../../docs/spec/ctx-respond-v1.md) has the
92
+ full surface. The TL;DR:
93
+
94
+ - `ctx.customer` — cached customer snapshot (≤10s stale). Read
95
+ `balance` / `remaining` / `plan` to branch on usage; call
96
+ `ctx.customer.fresh()` for a fresh fetch when staleness matters.
97
+ - `ctx.respond(data, options?)` — returns a branded envelope. `options`
98
+ carries `text` (override `content[0].text`), `nudge` (inline upsell
99
+ strip), and the reserved `units` (V1.1 variable-unit billing — V1
100
+ silently ignores).
101
+ - `ctx.gate(reason?)` — stops handler execution and routes a paywall
102
+ response through the adapter's `formatGate` channel. Rare — the
103
+ SDK normally fires the paywall automatically via `payable().mcp()`
104
+ pre-check.
105
+ - `ctx.emit(block)` / `ctx.progress(...)` / `ctx.signal` — reserved
106
+ surface. V1 queues (emit) or no-ops (progress / signal); V1.1 wires
107
+ them to SSE and transport cancellation without code changes.
108
+
109
+ ## What's in the box
110
+
111
+ | Export | Use when |
112
+ |---|---|
113
+ | `createSolvaPayMcpServer(opts)` | You want the batteries-included `McpServer` with every SolvaPay tool registered |
114
+ | `registerPayableTool(server, name, opts)` | You want to add a paywall-protected tool to an existing `McpServer`. `_meta.ui` is attached per-result on paywall and nudge responses only, so the iframe opens only when there's something to show. |
115
+
116
+ ## Peer dependencies
117
+
118
+ | Peer | Why |
119
+ |---|---|
120
+ | `@modelcontextprotocol/sdk` | The `McpServer` this package builds and returns |
121
+ | `@modelcontextprotocol/ext-apps` | `registerAppTool`, `registerAppResource`, the `RESOURCE_MIME_TYPE` constant |
122
+ | `@solvapay/mcp-core` | Neutral contracts and descriptor builder |
123
+ | `@solvapay/server` | `SolvaPay` factory + `PaywallError` runtime |
124
+ | `zod` | Tool `inputSchema` shape (required by the ext-apps helpers) |
125
+
126
+ ## Want to adapt another framework?
127
+
128
+ Use [`@solvapay/mcp-core`](../mcp-core) directly. `buildSolvaPayDescriptors`
129
+ returns a framework-neutral bundle; map it onto your framework's
130
+ `registerTool` / `registerResource` API in ~60 lines. This package is
131
+ itself that mapper for the official SDK.
package/dist/index.cjs ADDED
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createSolvaPayMcpServer: () => createSolvaPayMcpServer,
24
+ registerPayableTool: () => registerPayableTool
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/server.ts
29
+ var import_server2 = require("@modelcontextprotocol/ext-apps/server");
30
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
31
+ var import_mcp_core2 = require("@solvapay/mcp-core");
32
+
33
+ // src/registerPayableTool.ts
34
+ var import_server = require("@modelcontextprotocol/ext-apps/server");
35
+ var import_mcp_core = require("@solvapay/mcp-core");
36
+ function registerPayableTool(server, name, options) {
37
+ const {
38
+ solvaPay,
39
+ resourceUri,
40
+ schema,
41
+ product,
42
+ title,
43
+ description,
44
+ handler,
45
+ buildBootstrap,
46
+ getCustomerRef,
47
+ meta,
48
+ annotations,
49
+ icons
50
+ } = options;
51
+ const protectedHandler = (0, import_mcp_core.buildPayableHandler)(
52
+ solvaPay,
53
+ { product, resourceUri, buildBootstrap, getCustomerRef },
54
+ handler
55
+ );
56
+ const baseMeta = meta ?? {};
57
+ const baseUi = baseMeta.ui ?? {};
58
+ const hasIcons = icons !== void 0 && icons.length > 0;
59
+ const mergedUi = {
60
+ resourceUri,
61
+ ...baseUi,
62
+ ...hasIcons ? { icons } : {}
63
+ };
64
+ const toolMeta = { ...baseMeta, ui: mergedUi };
65
+ const effectiveAnnotations = {
66
+ readOnlyHint: true,
67
+ openWorldHint: true,
68
+ ...annotations
69
+ };
70
+ return (0, import_server.registerAppTool)(
71
+ server,
72
+ name,
73
+ // Note: `registerAppTool`'s config type is stricter than ours —
74
+ // casting so `title` / `description` stay optional and the input
75
+ // schema flows through correctly at the registration layer.
76
+ {
77
+ ...title !== void 0 ? { title } : {},
78
+ ...description !== void 0 ? { description } : {},
79
+ ...schema !== void 0 ? { inputSchema: schema } : {},
80
+ _meta: toolMeta,
81
+ annotations: effectiveAnnotations,
82
+ ...icons !== void 0 && icons.length > 0 ? { icons } : {}
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ },
85
+ async (args, extra) => await protectedHandler(args, extra)
86
+ );
87
+ }
88
+
89
+ // src/server.ts
90
+ function registerDescriptor(server, tool) {
91
+ const baseMeta = tool.meta ?? {};
92
+ const baseUi = baseMeta.ui ?? {};
93
+ const metaWithIcons = tool.icons && tool.icons.length > 0 ? { ...baseMeta, ui: { ...baseUi, icons: tool.icons } } : baseMeta;
94
+ (0, import_server2.registerAppTool)(
95
+ server,
96
+ tool.name,
97
+ {
98
+ ...tool.title !== void 0 ? { title: tool.title } : {},
99
+ description: tool.description,
100
+ inputSchema: tool.inputSchema,
101
+ _meta: metaWithIcons,
102
+ ...tool.annotations !== void 0 ? { annotations: tool.annotations } : {},
103
+ ...tool.icons !== void 0 ? { icons: tool.icons } : {}
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ },
106
+ // `SolvaPayCallToolResult` is a structural subset of the official
107
+ // SDK's `CallToolResult`; cast to erase the extra-narrow `resource`
108
+ // block typing the SDK expects on `{ type: 'resource' }` content.
109
+ async (args, extra) => await tool.handler(
110
+ args,
111
+ extra
112
+ )
113
+ );
114
+ }
115
+ function registerPromptDescriptor(server, prompt) {
116
+ const config = { description: prompt.description };
117
+ if (prompt.title !== void 0) config.title = prompt.title;
118
+ if (prompt.argsSchema !== void 0) config.argsSchema = prompt.argsSchema;
119
+ server.registerPrompt(
120
+ prompt.name,
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ config,
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ async (args) => await prompt.handler(args ?? {})
125
+ );
126
+ }
127
+ function registerDocsResource(server, docs) {
128
+ server.registerResource(
129
+ docs.name,
130
+ docs.uri,
131
+ {
132
+ ...docs.title !== void 0 ? { title: docs.title } : {},
133
+ description: docs.description,
134
+ mimeType: docs.mimeType
135
+ },
136
+ async () => ({
137
+ contents: [
138
+ {
139
+ uri: docs.uri,
140
+ mimeType: docs.mimeType,
141
+ text: await docs.readBody()
142
+ }
143
+ ]
144
+ })
145
+ );
146
+ }
147
+ function createSolvaPayMcpServer(options) {
148
+ const {
149
+ additionalTools,
150
+ registerPrompts = true,
151
+ registerDocsResources = true,
152
+ serverName,
153
+ serverVersion = "1.0.0",
154
+ ...descriptorOptions
155
+ } = options;
156
+ const { tools, resource, prompts, docsResources, buildBootstrapPayload } = (0, import_mcp_core2.buildSolvaPayDescriptors)(descriptorOptions);
157
+ const effectiveServerName = serverName ?? descriptorOptions.branding?.brandName ?? "solvapay-mcp-server";
158
+ const server = new import_mcp.McpServer({ name: effectiveServerName, version: serverVersion });
159
+ for (const tool of tools) {
160
+ registerDescriptor(server, tool);
161
+ }
162
+ if (registerPrompts) {
163
+ for (const prompt of prompts) {
164
+ registerPromptDescriptor(server, prompt);
165
+ }
166
+ }
167
+ if (registerDocsResources) {
168
+ for (const docs of docsResources) {
169
+ registerDocsResource(server, docs);
170
+ }
171
+ }
172
+ (0, import_server2.registerAppResource)(
173
+ server,
174
+ resource.uri,
175
+ resource.uri,
176
+ {
177
+ mimeType: import_server2.RESOURCE_MIME_TYPE,
178
+ _meta: {
179
+ ui: {
180
+ csp: resource.csp,
181
+ prefersBorder: true
182
+ }
183
+ }
184
+ },
185
+ async () => ({
186
+ contents: [
187
+ {
188
+ uri: resource.uri,
189
+ mimeType: import_server2.RESOURCE_MIME_TYPE,
190
+ text: await resource.readHtml(),
191
+ _meta: {
192
+ ui: {
193
+ csp: resource.csp,
194
+ prefersBorder: true
195
+ }
196
+ }
197
+ }
198
+ ]
199
+ })
200
+ );
201
+ if (additionalTools) {
202
+ const { solvaPay, productRef, resourceUri } = descriptorOptions;
203
+ const registerPayable = (name, opts) => {
204
+ registerPayableTool(server, name, {
205
+ solvaPay,
206
+ resourceUri,
207
+ ...opts,
208
+ product: opts.product ?? productRef,
209
+ buildBootstrap: opts.buildBootstrap ?? buildBootstrapPayload
210
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
211
+ });
212
+ };
213
+ additionalTools({ server, solvaPay, resourceUri, productRef, registerPayable });
214
+ }
215
+ return server;
216
+ }
217
+ // Annotate the CommonJS export names for ESM import in node:
218
+ 0 && (module.exports = {
219
+ createSolvaPayMcpServer,
220
+ registerPayableTool
221
+ });
@@ -0,0 +1,180 @@
1
+ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { ZodRawShapeCompat, AnySchema, ShapeOutput, SchemaOutput } from '@modelcontextprotocol/sdk/server/zod-compat.js';
3
+ import { PayableHandler, BuildBootstrapPayloadFn, McpToolExtra, SolvaPayToolAnnotations, SolvaPayToolIcon, BuildSolvaPayDescriptorsOptions } from '@solvapay/mcp-core';
4
+ export { ContentBlock, CustomerSnapshot, NudgeSpec, PayableHandler, ResponseContext, ResponseOptions, ResponseResult } from '@solvapay/mcp-core';
5
+ import { SolvaPay } from '@solvapay/server';
6
+
7
+ /**
8
+ * `registerPayableTool(server, name, options)` — one-liner for registering
9
+ * a paywall-protected MCP tool on the official `@modelcontextprotocol/sdk`
10
+ * `McpServer`.
11
+ *
12
+ * Always advertises `_meta.ui.resourceUri` at the descriptor level so
13
+ * descriptor-reading hosts (MCPJam's `ResultsPanel`, MCP App inspector)
14
+ * learn the widget exists and open the MCP App iframe when a paywall or
15
+ * nudge response lands. `buildPayableHandler` stamps the same metadata
16
+ * on the result envelope as a secondary signal for hosts that key off
17
+ * tool-call results (Claude Desktop, mcp-ui, ChatGPT Apps).
18
+ *
19
+ * Mirrors the positional-`name` shape of `registerAppTool` to keep the
20
+ * convention consistent across the ecosystem.
21
+ */
22
+
23
+ /**
24
+ * Projects the tool's `schema` (raw shape or already-constructed
25
+ * schema) into the `args` type the handler receives. When no schema
26
+ * is provided, falls back to `Record<string, unknown>` so handlers
27
+ * can still destructure without losing type-check.
28
+ */
29
+ type InferHandlerArgs<InputSchema> = [InputSchema] extends [undefined] ? Record<string, unknown> : InputSchema extends ZodRawShapeCompat ? ShapeOutput<InputSchema> : InputSchema extends AnySchema ? SchemaOutput<InputSchema> : Record<string, unknown>;
30
+ interface RegisterPayableToolOptions<InputSchema extends ZodRawShapeCompat | AnySchema | undefined = undefined, TData = unknown> {
31
+ /** The initialised SolvaPay instance used to build `payable({ product }).mcp(handler)`. */
32
+ solvaPay: SolvaPay;
33
+ /**
34
+ * UI resource URI the MCP host should open to render the paywall view.
35
+ * Typically `'ui://<app>/<resource>.html'`.
36
+ */
37
+ resourceUri: string;
38
+ /** Zod-compatible input schema (raw shape or discriminated schema). */
39
+ schema?: InputSchema;
40
+ /** SolvaPay product ref to protect this tool against. */
41
+ product: string;
42
+ /** Optional human-readable tool title for MCP listings. */
43
+ title?: string;
44
+ /** Optional tool description surfaced to the model. */
45
+ description?: string;
46
+ /**
47
+ * Business logic that runs once the caller is within limits. Receives
48
+ * parsed `args` (inferred from `schema` when provided) and a
49
+ * `ResponseContext`; must return the branded envelope produced by
50
+ * `ctx.respond(data, options?)`.
51
+ *
52
+ * `ctx` surfaces:
53
+ * - `ctx.customer` — cached snapshot (`balance`, `remaining`, `plan`,
54
+ * `.fresh()` for a round-trip).
55
+ * - `ctx.product` — bootstrap product projection.
56
+ * - `ctx.respond(data, options?)` — return an envelope. `options`
57
+ * carries `text` (override `content[0].text`), `nudge` (inline
58
+ * upsell strip), and the reserved `units` (V1.1 variable billing —
59
+ * V1 silently ignores).
60
+ * - `ctx.gate(reason?)` — stops handler execution and emits a
61
+ * paywall response through the adapter's `formatGate` channel.
62
+ * Rare — the SDK normally fires the paywall automatically via
63
+ * `payable().mcp()` pre-check.
64
+ * - `ctx.emit(block)` / `ctx.progress(...)` / `ctx.signal` — reserved
65
+ * streaming surface. V1 queues (emit) or no-ops (progress / signal);
66
+ * V1.1 wires them to SSE and transport cancellation.
67
+ *
68
+ * Throwing anything other than `PaywallError` surfaces as a tool-level
69
+ * error via `formatError`.
70
+ */
71
+ handler: PayableHandler<InferHandlerArgs<InputSchema>, TData>;
72
+ /**
73
+ * Builds the full `BootstrapPayload` embedded on paywall results so
74
+ * the React shell renders the paywall view directly from the gate
75
+ * response. Wire from
76
+ * `buildSolvaPayDescriptors(...).buildBootstrapPayload`.
77
+ */
78
+ buildBootstrap?: BuildBootstrapPayloadFn;
79
+ /**
80
+ * Override customer-ref extraction. Defaults to the MCP adapter's
81
+ * behavior (reads `extra.authInfo.extra.customer_ref`).
82
+ */
83
+ getCustomerRef?: (args: Record<string, unknown>, extra?: McpToolExtra) => string | Promise<string>;
84
+ /**
85
+ * Additional `_meta` merged onto the tool **descriptor** (the tool
86
+ * advertisement returned by `tools/list`).
87
+ *
88
+ * `registerPayableTool` always injects `ui.resourceUri` at the
89
+ * descriptor level so hosts that read widget metadata from
90
+ * `tools/list` (MCPJam's `ResultsPanel` reads
91
+ * `toolMeta.ui.resourceUri` from the tool definition) can open the
92
+ * MCP App iframe when a paywall/nudge response lands. Merchant
93
+ * `meta.ui.resourceUri` overrides the default if supplied.
94
+ */
95
+ meta?: Record<string, unknown>;
96
+ /**
97
+ * Portable MCP tool annotations. Defaults to
98
+ * `{ readOnlyHint: true, openWorldHint: true }` — sensible for a
99
+ * paywalled *data* tool that reads from the merchant's backend.
100
+ * Override for tools that mutate state (e.g. `submit_order`) with
101
+ * `annotations: { readOnlyHint: false, destructiveHint: true }`.
102
+ */
103
+ annotations?: SolvaPayToolAnnotations;
104
+ /**
105
+ * Brand icons surfaced on `tools/list`. Hosts that read tool
106
+ * metadata for the chrome strip (ChatGPT, Claude Desktop) swap the
107
+ * default placeholder for this asset. Pass a square logomark for
108
+ * best results. Merchants typically share one `icons[]` across
109
+ * every tool — consider a single branding source at the server
110
+ * level.
111
+ */
112
+ icons?: SolvaPayToolIcon[];
113
+ }
114
+ /**
115
+ * Register a paywall-protected tool on an MCP server.
116
+ */
117
+ declare function registerPayableTool<InputSchema extends ZodRawShapeCompat | AnySchema | undefined = undefined, TData = unknown>(server: McpServer, name: string, options: RegisterPayableToolOptions<InputSchema, TData>): RegisteredTool;
118
+
119
+ /**
120
+ * `createSolvaPayMcpServer` — batteries-included factory that
121
+ * registers the full SolvaPay transport + bootstrap tool surface on a
122
+ * fresh `McpServer` from the official `@modelcontextprotocol/sdk`,
123
+ * plus the UI resource the `open_*` tools reference.
124
+ *
125
+ * Internals are a thin mapper over `buildSolvaPayDescriptors` from
126
+ * `@solvapay/mcp-core` — this package is the only one importing
127
+ * `@modelcontextprotocol/*`.
128
+ */
129
+
130
+ /**
131
+ * Callback fired from the `additionalTools` hook with helpers bound for
132
+ * the current server + `solvaPay` instance.
133
+ */
134
+ interface AdditionalToolsContext {
135
+ server: McpServer;
136
+ solvaPay: SolvaPay;
137
+ resourceUri: string;
138
+ productRef: string;
139
+ /**
140
+ * `registerPayableTool` bound with `solvaPay` + `resourceUri` already
141
+ * provided, and `product` defaulting to the server's `productRef`.
142
+ *
143
+ * Zod `schema` flows through to the handler's `args` parameter so
144
+ * merchants get inferred arg types without a second declaration.
145
+ */
146
+ registerPayable: <InputSchema extends ZodRawShapeCompat | AnySchema | undefined = undefined, TData = unknown>(name: string, options: Omit<RegisterPayableToolOptions<InputSchema, TData>, 'solvaPay' | 'resourceUri' | 'product'> & {
147
+ product?: string;
148
+ }) => void;
149
+ }
150
+ interface CreateSolvaPayMcpServerOptions extends BuildSolvaPayDescriptorsOptions {
151
+ /**
152
+ * Integrator hook to register non-SolvaPay tools. The callback receives
153
+ * the built server plus a `registerPayable` helper bound for this
154
+ * instance.
155
+ */
156
+ additionalTools?: (ctx: AdditionalToolsContext) => void;
157
+ /**
158
+ * Register the slash-command prompts (`/upgrade`, `/manage_account`,
159
+ * `/topup`, `/activate_plan`) built from the descriptor bundle.
160
+ * Defaults to `true` — the prompts are additive and silently ignored
161
+ * by hosts without prompt support.
162
+ */
163
+ registerPrompts?: boolean;
164
+ /**
165
+ * Register the narrated `docs://solvapay/overview.md` resource so
166
+ * agents can `resources/read` before trying a tool. Defaults to
167
+ * `true` — pure narration, no side-effects.
168
+ */
169
+ registerDocsResources?: boolean;
170
+ /** Overrides the default `McpServer` name. */
171
+ serverName?: string;
172
+ /** Overrides the default `McpServer` version. */
173
+ serverVersion?: string;
174
+ }
175
+ /**
176
+ * Build the MCP server and register the full SolvaPay tool surface.
177
+ */
178
+ declare function createSolvaPayMcpServer(options: CreateSolvaPayMcpServerOptions): McpServer;
179
+
180
+ export { type AdditionalToolsContext, type CreateSolvaPayMcpServerOptions, type RegisterPayableToolOptions, createSolvaPayMcpServer, registerPayableTool };
@@ -0,0 +1,180 @@
1
+ import { McpServer, RegisteredTool } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { ZodRawShapeCompat, AnySchema, ShapeOutput, SchemaOutput } from '@modelcontextprotocol/sdk/server/zod-compat.js';
3
+ import { PayableHandler, BuildBootstrapPayloadFn, McpToolExtra, SolvaPayToolAnnotations, SolvaPayToolIcon, BuildSolvaPayDescriptorsOptions } from '@solvapay/mcp-core';
4
+ export { ContentBlock, CustomerSnapshot, NudgeSpec, PayableHandler, ResponseContext, ResponseOptions, ResponseResult } from '@solvapay/mcp-core';
5
+ import { SolvaPay } from '@solvapay/server';
6
+
7
+ /**
8
+ * `registerPayableTool(server, name, options)` — one-liner for registering
9
+ * a paywall-protected MCP tool on the official `@modelcontextprotocol/sdk`
10
+ * `McpServer`.
11
+ *
12
+ * Always advertises `_meta.ui.resourceUri` at the descriptor level so
13
+ * descriptor-reading hosts (MCPJam's `ResultsPanel`, MCP App inspector)
14
+ * learn the widget exists and open the MCP App iframe when a paywall or
15
+ * nudge response lands. `buildPayableHandler` stamps the same metadata
16
+ * on the result envelope as a secondary signal for hosts that key off
17
+ * tool-call results (Claude Desktop, mcp-ui, ChatGPT Apps).
18
+ *
19
+ * Mirrors the positional-`name` shape of `registerAppTool` to keep the
20
+ * convention consistent across the ecosystem.
21
+ */
22
+
23
+ /**
24
+ * Projects the tool's `schema` (raw shape or already-constructed
25
+ * schema) into the `args` type the handler receives. When no schema
26
+ * is provided, falls back to `Record<string, unknown>` so handlers
27
+ * can still destructure without losing type-check.
28
+ */
29
+ type InferHandlerArgs<InputSchema> = [InputSchema] extends [undefined] ? Record<string, unknown> : InputSchema extends ZodRawShapeCompat ? ShapeOutput<InputSchema> : InputSchema extends AnySchema ? SchemaOutput<InputSchema> : Record<string, unknown>;
30
+ interface RegisterPayableToolOptions<InputSchema extends ZodRawShapeCompat | AnySchema | undefined = undefined, TData = unknown> {
31
+ /** The initialised SolvaPay instance used to build `payable({ product }).mcp(handler)`. */
32
+ solvaPay: SolvaPay;
33
+ /**
34
+ * UI resource URI the MCP host should open to render the paywall view.
35
+ * Typically `'ui://<app>/<resource>.html'`.
36
+ */
37
+ resourceUri: string;
38
+ /** Zod-compatible input schema (raw shape or discriminated schema). */
39
+ schema?: InputSchema;
40
+ /** SolvaPay product ref to protect this tool against. */
41
+ product: string;
42
+ /** Optional human-readable tool title for MCP listings. */
43
+ title?: string;
44
+ /** Optional tool description surfaced to the model. */
45
+ description?: string;
46
+ /**
47
+ * Business logic that runs once the caller is within limits. Receives
48
+ * parsed `args` (inferred from `schema` when provided) and a
49
+ * `ResponseContext`; must return the branded envelope produced by
50
+ * `ctx.respond(data, options?)`.
51
+ *
52
+ * `ctx` surfaces:
53
+ * - `ctx.customer` — cached snapshot (`balance`, `remaining`, `plan`,
54
+ * `.fresh()` for a round-trip).
55
+ * - `ctx.product` — bootstrap product projection.
56
+ * - `ctx.respond(data, options?)` — return an envelope. `options`
57
+ * carries `text` (override `content[0].text`), `nudge` (inline
58
+ * upsell strip), and the reserved `units` (V1.1 variable billing —
59
+ * V1 silently ignores).
60
+ * - `ctx.gate(reason?)` — stops handler execution and emits a
61
+ * paywall response through the adapter's `formatGate` channel.
62
+ * Rare — the SDK normally fires the paywall automatically via
63
+ * `payable().mcp()` pre-check.
64
+ * - `ctx.emit(block)` / `ctx.progress(...)` / `ctx.signal` — reserved
65
+ * streaming surface. V1 queues (emit) or no-ops (progress / signal);
66
+ * V1.1 wires them to SSE and transport cancellation.
67
+ *
68
+ * Throwing anything other than `PaywallError` surfaces as a tool-level
69
+ * error via `formatError`.
70
+ */
71
+ handler: PayableHandler<InferHandlerArgs<InputSchema>, TData>;
72
+ /**
73
+ * Builds the full `BootstrapPayload` embedded on paywall results so
74
+ * the React shell renders the paywall view directly from the gate
75
+ * response. Wire from
76
+ * `buildSolvaPayDescriptors(...).buildBootstrapPayload`.
77
+ */
78
+ buildBootstrap?: BuildBootstrapPayloadFn;
79
+ /**
80
+ * Override customer-ref extraction. Defaults to the MCP adapter's
81
+ * behavior (reads `extra.authInfo.extra.customer_ref`).
82
+ */
83
+ getCustomerRef?: (args: Record<string, unknown>, extra?: McpToolExtra) => string | Promise<string>;
84
+ /**
85
+ * Additional `_meta` merged onto the tool **descriptor** (the tool
86
+ * advertisement returned by `tools/list`).
87
+ *
88
+ * `registerPayableTool` always injects `ui.resourceUri` at the
89
+ * descriptor level so hosts that read widget metadata from
90
+ * `tools/list` (MCPJam's `ResultsPanel` reads
91
+ * `toolMeta.ui.resourceUri` from the tool definition) can open the
92
+ * MCP App iframe when a paywall/nudge response lands. Merchant
93
+ * `meta.ui.resourceUri` overrides the default if supplied.
94
+ */
95
+ meta?: Record<string, unknown>;
96
+ /**
97
+ * Portable MCP tool annotations. Defaults to
98
+ * `{ readOnlyHint: true, openWorldHint: true }` — sensible for a
99
+ * paywalled *data* tool that reads from the merchant's backend.
100
+ * Override for tools that mutate state (e.g. `submit_order`) with
101
+ * `annotations: { readOnlyHint: false, destructiveHint: true }`.
102
+ */
103
+ annotations?: SolvaPayToolAnnotations;
104
+ /**
105
+ * Brand icons surfaced on `tools/list`. Hosts that read tool
106
+ * metadata for the chrome strip (ChatGPT, Claude Desktop) swap the
107
+ * default placeholder for this asset. Pass a square logomark for
108
+ * best results. Merchants typically share one `icons[]` across
109
+ * every tool — consider a single branding source at the server
110
+ * level.
111
+ */
112
+ icons?: SolvaPayToolIcon[];
113
+ }
114
+ /**
115
+ * Register a paywall-protected tool on an MCP server.
116
+ */
117
+ declare function registerPayableTool<InputSchema extends ZodRawShapeCompat | AnySchema | undefined = undefined, TData = unknown>(server: McpServer, name: string, options: RegisterPayableToolOptions<InputSchema, TData>): RegisteredTool;
118
+
119
+ /**
120
+ * `createSolvaPayMcpServer` — batteries-included factory that
121
+ * registers the full SolvaPay transport + bootstrap tool surface on a
122
+ * fresh `McpServer` from the official `@modelcontextprotocol/sdk`,
123
+ * plus the UI resource the `open_*` tools reference.
124
+ *
125
+ * Internals are a thin mapper over `buildSolvaPayDescriptors` from
126
+ * `@solvapay/mcp-core` — this package is the only one importing
127
+ * `@modelcontextprotocol/*`.
128
+ */
129
+
130
+ /**
131
+ * Callback fired from the `additionalTools` hook with helpers bound for
132
+ * the current server + `solvaPay` instance.
133
+ */
134
+ interface AdditionalToolsContext {
135
+ server: McpServer;
136
+ solvaPay: SolvaPay;
137
+ resourceUri: string;
138
+ productRef: string;
139
+ /**
140
+ * `registerPayableTool` bound with `solvaPay` + `resourceUri` already
141
+ * provided, and `product` defaulting to the server's `productRef`.
142
+ *
143
+ * Zod `schema` flows through to the handler's `args` parameter so
144
+ * merchants get inferred arg types without a second declaration.
145
+ */
146
+ registerPayable: <InputSchema extends ZodRawShapeCompat | AnySchema | undefined = undefined, TData = unknown>(name: string, options: Omit<RegisterPayableToolOptions<InputSchema, TData>, 'solvaPay' | 'resourceUri' | 'product'> & {
147
+ product?: string;
148
+ }) => void;
149
+ }
150
+ interface CreateSolvaPayMcpServerOptions extends BuildSolvaPayDescriptorsOptions {
151
+ /**
152
+ * Integrator hook to register non-SolvaPay tools. The callback receives
153
+ * the built server plus a `registerPayable` helper bound for this
154
+ * instance.
155
+ */
156
+ additionalTools?: (ctx: AdditionalToolsContext) => void;
157
+ /**
158
+ * Register the slash-command prompts (`/upgrade`, `/manage_account`,
159
+ * `/topup`, `/activate_plan`) built from the descriptor bundle.
160
+ * Defaults to `true` — the prompts are additive and silently ignored
161
+ * by hosts without prompt support.
162
+ */
163
+ registerPrompts?: boolean;
164
+ /**
165
+ * Register the narrated `docs://solvapay/overview.md` resource so
166
+ * agents can `resources/read` before trying a tool. Defaults to
167
+ * `true` — pure narration, no side-effects.
168
+ */
169
+ registerDocsResources?: boolean;
170
+ /** Overrides the default `McpServer` name. */
171
+ serverName?: string;
172
+ /** Overrides the default `McpServer` version. */
173
+ serverVersion?: string;
174
+ }
175
+ /**
176
+ * Build the MCP server and register the full SolvaPay tool surface.
177
+ */
178
+ declare function createSolvaPayMcpServer(options: CreateSolvaPayMcpServerOptions): McpServer;
179
+
180
+ export { type AdditionalToolsContext, type CreateSolvaPayMcpServerOptions, type RegisterPayableToolOptions, createSolvaPayMcpServer, registerPayableTool };
package/dist/index.js ADDED
@@ -0,0 +1,201 @@
1
+ // src/server.ts
2
+ import {
3
+ registerAppResource,
4
+ registerAppTool as registerAppTool2,
5
+ RESOURCE_MIME_TYPE
6
+ } from "@modelcontextprotocol/ext-apps/server";
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import {
9
+ buildSolvaPayDescriptors
10
+ } from "@solvapay/mcp-core";
11
+
12
+ // src/registerPayableTool.ts
13
+ import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";
14
+ import {
15
+ buildPayableHandler
16
+ } from "@solvapay/mcp-core";
17
+ function registerPayableTool(server, name, options) {
18
+ const {
19
+ solvaPay,
20
+ resourceUri,
21
+ schema,
22
+ product,
23
+ title,
24
+ description,
25
+ handler,
26
+ buildBootstrap,
27
+ getCustomerRef,
28
+ meta,
29
+ annotations,
30
+ icons
31
+ } = options;
32
+ const protectedHandler = buildPayableHandler(
33
+ solvaPay,
34
+ { product, resourceUri, buildBootstrap, getCustomerRef },
35
+ handler
36
+ );
37
+ const baseMeta = meta ?? {};
38
+ const baseUi = baseMeta.ui ?? {};
39
+ const hasIcons = icons !== void 0 && icons.length > 0;
40
+ const mergedUi = {
41
+ resourceUri,
42
+ ...baseUi,
43
+ ...hasIcons ? { icons } : {}
44
+ };
45
+ const toolMeta = { ...baseMeta, ui: mergedUi };
46
+ const effectiveAnnotations = {
47
+ readOnlyHint: true,
48
+ openWorldHint: true,
49
+ ...annotations
50
+ };
51
+ return registerAppTool(
52
+ server,
53
+ name,
54
+ // Note: `registerAppTool`'s config type is stricter than ours —
55
+ // casting so `title` / `description` stay optional and the input
56
+ // schema flows through correctly at the registration layer.
57
+ {
58
+ ...title !== void 0 ? { title } : {},
59
+ ...description !== void 0 ? { description } : {},
60
+ ...schema !== void 0 ? { inputSchema: schema } : {},
61
+ _meta: toolMeta,
62
+ annotations: effectiveAnnotations,
63
+ ...icons !== void 0 && icons.length > 0 ? { icons } : {}
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ },
66
+ async (args, extra) => await protectedHandler(args, extra)
67
+ );
68
+ }
69
+
70
+ // src/server.ts
71
+ function registerDescriptor(server, tool) {
72
+ const baseMeta = tool.meta ?? {};
73
+ const baseUi = baseMeta.ui ?? {};
74
+ const metaWithIcons = tool.icons && tool.icons.length > 0 ? { ...baseMeta, ui: { ...baseUi, icons: tool.icons } } : baseMeta;
75
+ registerAppTool2(
76
+ server,
77
+ tool.name,
78
+ {
79
+ ...tool.title !== void 0 ? { title: tool.title } : {},
80
+ description: tool.description,
81
+ inputSchema: tool.inputSchema,
82
+ _meta: metaWithIcons,
83
+ ...tool.annotations !== void 0 ? { annotations: tool.annotations } : {},
84
+ ...tool.icons !== void 0 ? { icons: tool.icons } : {}
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ },
87
+ // `SolvaPayCallToolResult` is a structural subset of the official
88
+ // SDK's `CallToolResult`; cast to erase the extra-narrow `resource`
89
+ // block typing the SDK expects on `{ type: 'resource' }` content.
90
+ async (args, extra) => await tool.handler(
91
+ args,
92
+ extra
93
+ )
94
+ );
95
+ }
96
+ function registerPromptDescriptor(server, prompt) {
97
+ const config = { description: prompt.description };
98
+ if (prompt.title !== void 0) config.title = prompt.title;
99
+ if (prompt.argsSchema !== void 0) config.argsSchema = prompt.argsSchema;
100
+ server.registerPrompt(
101
+ prompt.name,
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ config,
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ async (args) => await prompt.handler(args ?? {})
106
+ );
107
+ }
108
+ function registerDocsResource(server, docs) {
109
+ server.registerResource(
110
+ docs.name,
111
+ docs.uri,
112
+ {
113
+ ...docs.title !== void 0 ? { title: docs.title } : {},
114
+ description: docs.description,
115
+ mimeType: docs.mimeType
116
+ },
117
+ async () => ({
118
+ contents: [
119
+ {
120
+ uri: docs.uri,
121
+ mimeType: docs.mimeType,
122
+ text: await docs.readBody()
123
+ }
124
+ ]
125
+ })
126
+ );
127
+ }
128
+ function createSolvaPayMcpServer(options) {
129
+ const {
130
+ additionalTools,
131
+ registerPrompts = true,
132
+ registerDocsResources = true,
133
+ serverName,
134
+ serverVersion = "1.0.0",
135
+ ...descriptorOptions
136
+ } = options;
137
+ const { tools, resource, prompts, docsResources, buildBootstrapPayload } = buildSolvaPayDescriptors(descriptorOptions);
138
+ const effectiveServerName = serverName ?? descriptorOptions.branding?.brandName ?? "solvapay-mcp-server";
139
+ const server = new McpServer({ name: effectiveServerName, version: serverVersion });
140
+ for (const tool of tools) {
141
+ registerDescriptor(server, tool);
142
+ }
143
+ if (registerPrompts) {
144
+ for (const prompt of prompts) {
145
+ registerPromptDescriptor(server, prompt);
146
+ }
147
+ }
148
+ if (registerDocsResources) {
149
+ for (const docs of docsResources) {
150
+ registerDocsResource(server, docs);
151
+ }
152
+ }
153
+ registerAppResource(
154
+ server,
155
+ resource.uri,
156
+ resource.uri,
157
+ {
158
+ mimeType: RESOURCE_MIME_TYPE,
159
+ _meta: {
160
+ ui: {
161
+ csp: resource.csp,
162
+ prefersBorder: true
163
+ }
164
+ }
165
+ },
166
+ async () => ({
167
+ contents: [
168
+ {
169
+ uri: resource.uri,
170
+ mimeType: RESOURCE_MIME_TYPE,
171
+ text: await resource.readHtml(),
172
+ _meta: {
173
+ ui: {
174
+ csp: resource.csp,
175
+ prefersBorder: true
176
+ }
177
+ }
178
+ }
179
+ ]
180
+ })
181
+ );
182
+ if (additionalTools) {
183
+ const { solvaPay, productRef, resourceUri } = descriptorOptions;
184
+ const registerPayable = (name, opts) => {
185
+ registerPayableTool(server, name, {
186
+ solvaPay,
187
+ resourceUri,
188
+ ...opts,
189
+ product: opts.product ?? productRef,
190
+ buildBootstrap: opts.buildBootstrap ?? buildBootstrapPayload
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
+ });
193
+ };
194
+ additionalTools({ server, solvaPay, resourceUri, productRef, registerPayable });
195
+ }
196
+ return server;
197
+ }
198
+ export {
199
+ createSolvaPayMcpServer,
200
+ registerPayableTool
201
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@solvapay/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Official @modelcontextprotocol/sdk + @modelcontextprotocol/ext-apps adapter for the SolvaPay MCP toolbox (createSolvaPayMcpServer, registerPayableTool).",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/solvapay/solvapay-sdk.git",
26
+ "directory": "packages/mcp"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.17"
30
+ },
31
+ "sideEffects": false,
32
+ "dependencies": {
33
+ "@solvapay/mcp-core": "0.1.0"
34
+ },
35
+ "peerDependencies": {
36
+ "@modelcontextprotocol/ext-apps": "^1.5.0",
37
+ "@modelcontextprotocol/sdk": "^1.28.0",
38
+ "zod": "^3.25.0 || ^4.0.0",
39
+ "@solvapay/server": "1.0.8-preview.10"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "zod": {
43
+ "optional": true
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@modelcontextprotocol/ext-apps": "^1.5.0",
48
+ "@modelcontextprotocol/sdk": "^1.29.0",
49
+ "tsup": "^8.5.1",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^4.1.2",
52
+ "zod": "^4.3.6",
53
+ "@solvapay/server": "1.0.8-preview.10",
54
+ "@solvapay/test-utils": "^0.0.0"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup",
58
+ "dev": "tsup --watch",
59
+ "test": "vitest run",
60
+ "test:unit": "vitest run",
61
+ "test:watch": "vitest",
62
+ "lint": "eslint src",
63
+ "lint:fix": "eslint src --fix"
64
+ }
65
+ }