@solvapay/mcp-core 0.2.3 → 0.2.4
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/dist/index.cjs +41 -3
- package/dist/index.d.cts +122 -14
- package/dist/index.d.ts +122 -14
- package/dist/index.js +40 -3
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -53,6 +53,7 @@ __export(index_exports, {
|
|
|
53
53
|
createBuildBootstrapPayload: () => createBuildBootstrapPayload,
|
|
54
54
|
decodeJwtPayload: () => decodeJwtPayload,
|
|
55
55
|
defaultGetCustomerRef: () => defaultGetCustomerRef,
|
|
56
|
+
defaultIsChatGptRequest: () => defaultIsChatGptRequest,
|
|
56
57
|
deriveIcons: () => deriveIcons,
|
|
57
58
|
enrichPurchase: () => enrichPurchase,
|
|
58
59
|
extractBearerToken: () => extractBearerToken,
|
|
@@ -503,9 +504,24 @@ function mergeCsp(overrides, apiBaseUrl) {
|
|
|
503
504
|
}
|
|
504
505
|
|
|
505
506
|
// src/hideToolsByAudience.ts
|
|
506
|
-
|
|
507
|
+
var CHATGPT_CLIENT_RE = /openai-mcp/i;
|
|
508
|
+
function readHeader(headers, name) {
|
|
509
|
+
if (!headers) return void 0;
|
|
510
|
+
const raw = headers[name] ?? headers[name.toLowerCase()];
|
|
511
|
+
if (Array.isArray(raw)) return raw[0];
|
|
512
|
+
return typeof raw === "string" ? raw : void 0;
|
|
513
|
+
}
|
|
514
|
+
function defaultIsChatGptRequest(ctx) {
|
|
515
|
+
const ua = readHeader(ctx.extra?.requestInfo?.headers, "user-agent");
|
|
516
|
+
if (ua && CHATGPT_CLIENT_RE.test(ua)) return true;
|
|
517
|
+
const clientVersion = ctx.server?.server?.getClientVersion?.();
|
|
518
|
+
const clientName = typeof clientVersion?.name === "string" ? clientVersion.name : void 0;
|
|
519
|
+
return clientName !== void 0 && CHATGPT_CLIENT_RE.test(clientName);
|
|
520
|
+
}
|
|
521
|
+
function applyHideToolsByAudience(server, audiences, options = {}) {
|
|
507
522
|
if (!audiences || audiences.length === 0) return;
|
|
508
523
|
const hidden = new Set(audiences);
|
|
524
|
+
const bypassWhen = options.bypassWhen ?? defaultIsChatGptRequest;
|
|
509
525
|
const inner = server.server;
|
|
510
526
|
if (!inner || typeof inner !== "object" || !(inner._requestHandlers instanceof Map)) {
|
|
511
527
|
return;
|
|
@@ -513,8 +529,26 @@ function applyHideToolsByAudience(server, audiences) {
|
|
|
513
529
|
const handlers = inner._requestHandlers;
|
|
514
530
|
const original = handlers.get("tools/list");
|
|
515
531
|
if (!original) return;
|
|
532
|
+
const warned = /* @__PURE__ */ new Set();
|
|
516
533
|
handlers.set("tools/list", async (req, extra) => {
|
|
517
534
|
const res = await original(req, extra);
|
|
535
|
+
if (bypassWhen({
|
|
536
|
+
server,
|
|
537
|
+
extra
|
|
538
|
+
})) {
|
|
539
|
+
const ua = readHeader(
|
|
540
|
+
extra?.requestInfo?.headers,
|
|
541
|
+
"user-agent"
|
|
542
|
+
);
|
|
543
|
+
const context = ua ? `ua=${ua}` : "no user-agent";
|
|
544
|
+
if (!warned.has(context)) {
|
|
545
|
+
warned.add(context);
|
|
546
|
+
console.warn(
|
|
547
|
+
`[solvapay/mcp] hideToolsByAudience filter bypassed (${context}); returning full tools/list catalog.`
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
return res;
|
|
551
|
+
}
|
|
518
552
|
const tools = Array.isArray(res?.tools) ? res.tools : [];
|
|
519
553
|
return {
|
|
520
554
|
...res,
|
|
@@ -766,7 +800,10 @@ function buildSolvaPayDescriptors(options) {
|
|
|
766
800
|
handler: async (args, extra) => trace(name, args, extra, async () => {
|
|
767
801
|
const mode = parseMode(args.mode);
|
|
768
802
|
const data = await buildBootstrapPayload(view, extra);
|
|
769
|
-
return narratedToolResult(name, data, mode,
|
|
803
|
+
return narratedToolResult(name, data, mode, {
|
|
804
|
+
...toolMeta,
|
|
805
|
+
"openai/widgetSessionId": crypto.randomUUID()
|
|
806
|
+
});
|
|
770
807
|
})
|
|
771
808
|
});
|
|
772
809
|
};
|
|
@@ -967,7 +1004,7 @@ function buildSolvaPayDescriptors(options) {
|
|
|
967
1004
|
MCP_TOOL_NAMES.activatePlan,
|
|
968
1005
|
await buildBootstrapPayload("checkout", extra),
|
|
969
1006
|
mode,
|
|
970
|
-
toolMeta
|
|
1007
|
+
{ ...toolMeta, "openai/widgetSessionId": crypto.randomUUID() }
|
|
971
1008
|
);
|
|
972
1009
|
}
|
|
973
1010
|
const auth = requireCustomerRef(extra);
|
|
@@ -1377,6 +1414,7 @@ function buildAuthInfoFromBearer(authorization, options = {}) {
|
|
|
1377
1414
|
createBuildBootstrapPayload,
|
|
1378
1415
|
decodeJwtPayload,
|
|
1379
1416
|
defaultGetCustomerRef,
|
|
1417
|
+
defaultIsChatGptRequest,
|
|
1380
1418
|
deriveIcons,
|
|
1381
1419
|
enrichPurchase,
|
|
1382
1420
|
extractBearerToken,
|
package/dist/index.d.cts
CHANGED
|
@@ -734,6 +734,12 @@ declare function parseMode(raw: unknown): SolvaPayToolMode;
|
|
|
734
734
|
*
|
|
735
735
|
* The narrator is picked by the `tool` name; unknown tools fall back
|
|
736
736
|
* to the JSON dump that `toolResult` produces today.
|
|
737
|
+
*
|
|
738
|
+
* Meta keys other than `ui` (notably `openai/widgetSessionId`, the
|
|
739
|
+
* ChatGPT MCP routing-bug workaround stamped by intent tools — see
|
|
740
|
+
* the descriptors.ts top-of-file comment) are preserved across all
|
|
741
|
+
* three modes. Don't strip additional `_meta` keys here without
|
|
742
|
+
* checking `descriptors.ts` first.
|
|
737
743
|
*/
|
|
738
744
|
declare function narratedToolResult(tool: IntentTool | string, data: BootstrapPayload, mode?: SolvaPayToolMode, baseMeta?: Record<string, unknown> | undefined): SolvaPayCallToolResult;
|
|
739
745
|
/**
|
|
@@ -861,7 +867,7 @@ declare const SOLVAPAY_DEFAULT_CSP: Required<SolvaPayMcpCsp>;
|
|
|
861
867
|
declare function mergeCsp(overrides: SolvaPayMcpCsp | undefined, apiBaseUrl?: string): Required<SolvaPayMcpCsp>;
|
|
862
868
|
|
|
863
869
|
/**
|
|
864
|
-
* `applyHideToolsByAudience(server, audiences)` — wraps the
|
|
870
|
+
* `applyHideToolsByAudience(server, audiences, options?)` — wraps the
|
|
865
871
|
* `tools/list` request handler on an `@modelcontextprotocol/sdk`
|
|
866
872
|
* `McpServer` so tool descriptors whose `_meta.audience` matches one
|
|
867
873
|
* of the supplied values are filtered out of the response.
|
|
@@ -869,21 +875,50 @@ declare function mergeCsp(overrides: SolvaPayMcpCsp | undefined, apiBaseUrl?: st
|
|
|
869
875
|
* The tools stay `enabled: true` on the server, so `tools/call` still
|
|
870
876
|
* reaches their handlers — this helper only affects the `tools/list`
|
|
871
877
|
* response shape. Use `['ui']` when deploying to a text-host MCP
|
|
872
|
-
* client (Claude Desktop, MCPJam, ChatGPT
|
|
878
|
+
* client (Claude Desktop, MCPJam, ChatGPT connector) that won't
|
|
873
879
|
* embed the SolvaPay iframe surface, while still allowing the iframe
|
|
874
880
|
* to invoke the hidden transport tools (`create_payment_intent` etc.)
|
|
875
881
|
* for server-side work.
|
|
876
882
|
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
* `
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
883
|
+
* # ChatGPT auto-bypass
|
|
884
|
+
*
|
|
885
|
+
* ChatGPT's Custom Connector gateway re-validates iframe-initiated
|
|
886
|
+
* `tools/call` against the cached `tools/list` catalog. A tool hidden
|
|
887
|
+
* from `tools/list` becomes uncallable from the embedded iframe and
|
|
888
|
+
* surfaces in the UI as `MCP error -32000: MCP Resource not found`.
|
|
889
|
+
*
|
|
890
|
+
* To keep the cleaner LLM-facing catalog on every other host while
|
|
891
|
+
* the iframe still works on ChatGPT, the default behaviour
|
|
892
|
+
* automatically returns the **full** unfiltered catalog when the
|
|
893
|
+
* incoming `tools/list` request originates from ChatGPT — detected by
|
|
894
|
+
* matching `request.headers['user-agent']` and the post-`initialize`
|
|
895
|
+
* `server.getClientVersion().name` against `/openai-mcp/i`. The first
|
|
896
|
+
* triggers on the discovery `tools/list` ChatGPT issues before
|
|
897
|
+
* `initialize` (so `getClientVersion()` is empty); the second is a
|
|
898
|
+
* defence-in-depth fallback for any future relay that strips the
|
|
899
|
+
* client UA.
|
|
900
|
+
*
|
|
901
|
+
* The User-Agent shape was confirmed live against ChatGPT's MCP
|
|
902
|
+
* runtime (`openai-mcp/1.0.0 (ChatGPT)` as of 2026-05). The pattern
|
|
903
|
+
* is intentionally broad so a UA bump to `openai-mcp/2.x` keeps
|
|
904
|
+
* working without code changes.
|
|
905
|
+
*
|
|
906
|
+
* Override the detection by passing `bypassWhen` — useful when a
|
|
907
|
+
* future iframe-capable host needs the same treatment, or when
|
|
908
|
+
* ChatGPT-served deployments want the LLM-narrow catalog regardless
|
|
909
|
+
* (`bypassWhen: () => false`).
|
|
910
|
+
*
|
|
911
|
+
* # Internal-map reach-in rationale
|
|
912
|
+
*
|
|
913
|
+
* `Protocol.setRequestHandler` contains an `assertCanSetRequestHandler`
|
|
914
|
+
* guard that fails when the SDK has already registered a handler for
|
|
915
|
+
* the method during registration — i.e. `tools/list` after any tool
|
|
916
|
+
* registration. The guard only fires through the public wrapper; the
|
|
917
|
+
* underlying `_requestHandlers` map accepts a replacement silently.
|
|
918
|
+
* Touching the map directly therefore sidesteps the guard without
|
|
919
|
+
* going through public API surface that could change in a minor SDK
|
|
920
|
+
* bump. The one piece of SDK-internal knowledge we live with until
|
|
921
|
+
* the SDK ships a first-class "replace handler" affordance.
|
|
887
922
|
*
|
|
888
923
|
* No-op when `audiences` is empty or falsy.
|
|
889
924
|
*
|
|
@@ -892,7 +927,58 @@ declare function mergeCsp(overrides: SolvaPayMcpCsp | undefined, apiBaseUrl?: st
|
|
|
892
927
|
* `@solvapay/mcp/fetch` (unified `createSolvaPayMcpFetch`) can apply
|
|
893
928
|
* the same filter without each re-implementing the reach-in.
|
|
894
929
|
*/
|
|
895
|
-
|
|
930
|
+
/**
|
|
931
|
+
* Headers shape exposed by the MCP SDK's `RequestHandlerExtra.requestInfo`.
|
|
932
|
+
* Mirrors `IsomorphicHeaders` from the SDK without taking a type dep.
|
|
933
|
+
*/
|
|
934
|
+
type IsomorphicHeaders = Record<string, string | string[] | undefined>;
|
|
935
|
+
/**
|
|
936
|
+
* Subset of `RequestHandlerExtra` (from `@modelcontextprotocol/sdk`)
|
|
937
|
+
* that the bypass predicate inspects. Typed loosely so we don't
|
|
938
|
+
* couple to the SDK's exact shape.
|
|
939
|
+
*/
|
|
940
|
+
interface ApplyHideToolsByAudienceExtra {
|
|
941
|
+
requestInfo?: {
|
|
942
|
+
headers?: IsomorphicHeaders;
|
|
943
|
+
} | undefined;
|
|
944
|
+
[key: string]: unknown;
|
|
945
|
+
}
|
|
946
|
+
interface ApplyHideToolsByAudienceContext {
|
|
947
|
+
/** The MCP server instance the filter is being applied to. */
|
|
948
|
+
server: unknown;
|
|
949
|
+
/**
|
|
950
|
+
* The `RequestHandlerExtra` the SDK passed to the wrapped
|
|
951
|
+
* `tools/list` handler. May be undefined for non-HTTP transports
|
|
952
|
+
* (e.g. stdio) where there's no request information.
|
|
953
|
+
*/
|
|
954
|
+
extra?: ApplyHideToolsByAudienceExtra;
|
|
955
|
+
}
|
|
956
|
+
type HideToolsByAudienceBypass = (ctx: ApplyHideToolsByAudienceContext) => boolean;
|
|
957
|
+
interface ApplyHideToolsByAudienceOptions {
|
|
958
|
+
/**
|
|
959
|
+
* When this predicate returns `true` for an incoming `tools/list`
|
|
960
|
+
* request, the audience filter is skipped and the full catalog is
|
|
961
|
+
* returned. Defaults to `defaultIsChatGptRequest` — see the file
|
|
962
|
+
* header for the rationale.
|
|
963
|
+
*
|
|
964
|
+
* Pass an integrator-supplied predicate to extend the bypass to
|
|
965
|
+
* another host (e.g. a future iframe-capable client), or `() =>
|
|
966
|
+
* false` to apply the filter unconditionally.
|
|
967
|
+
*/
|
|
968
|
+
bypassWhen?: HideToolsByAudienceBypass;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Default `bypassWhen` — returns true when the incoming request looks
|
|
972
|
+
* like it's coming from ChatGPT's MCP runtime. Two signals:
|
|
973
|
+
*
|
|
974
|
+
* 1. HTTP `User-Agent` header (works on the pre-`initialize`
|
|
975
|
+
* discovery `tools/list`, so we have a signal even before the
|
|
976
|
+
* SDK's `getClientVersion()` is populated).
|
|
977
|
+
* 2. `server.getClientVersion()?.name` (covers any host that relays
|
|
978
|
+
* ChatGPT requests without forwarding the upstream UA).
|
|
979
|
+
*/
|
|
980
|
+
declare function defaultIsChatGptRequest(ctx: ApplyHideToolsByAudienceContext): boolean;
|
|
981
|
+
declare function applyHideToolsByAudience(server: unknown, audiences: readonly string[] | undefined, options?: ApplyHideToolsByAudienceOptions): void;
|
|
896
982
|
|
|
897
983
|
/**
|
|
898
984
|
* `buildSolvaPayDescriptors(options)` — framework-neutral tool surface
|
|
@@ -905,6 +991,28 @@ declare function applyHideToolsByAudience(server: unknown, audiences: readonly s
|
|
|
905
991
|
* mechanical difference: instead of calling `registerAppTool(server, ...)`,
|
|
906
992
|
* we push `{ name, handler, ... }` onto a `tools[]` array the adapter
|
|
907
993
|
* iterates.
|
|
994
|
+
*
|
|
995
|
+
* ----
|
|
996
|
+
*
|
|
997
|
+
* `_meta["openai/widgetSessionId"]` workaround. Every intent-tool
|
|
998
|
+
* response stamps a freshly-minted UUID on `_meta["openai/widgetSessionId"]`.
|
|
999
|
+
* This is a low-risk forward-looking workaround for the ChatGPT MCP
|
|
1000
|
+
* connector's stale `link_<id>` routing bug, where the host returns
|
|
1001
|
+
* `-32000 MCP Resource not found` on the second `tools/call` of a
|
|
1002
|
+
* session even though the call never reaches the server. A fresh UUID
|
|
1003
|
+
* per invocation gives the host a routing key that changes every call,
|
|
1004
|
+
* which the OpenAI Apps SDK community thread reports unsticks the
|
|
1005
|
+
* failure mode.
|
|
1006
|
+
*
|
|
1007
|
+
* Sources:
|
|
1008
|
+
* - https://community.openai.com/t/connector-tool-calls-generating-fresh-mcp-session-each-invocation/1364975
|
|
1009
|
+
* - https://github.com/openai/openai-apps-sdk-examples/issues/165
|
|
1010
|
+
* - https://developers.openai.com/apps-sdk/reference/ (`_meta` payload)
|
|
1011
|
+
* - openai/openai-apps-sdk-examples shopping_cart_python uses the
|
|
1012
|
+
* same `meta["openai/widgetSessionId"]` shape.
|
|
1013
|
+
*
|
|
1014
|
+
* Removable once the upstream bug ships a fix; safe on any host that
|
|
1015
|
+
* doesn't consume the key.
|
|
908
1016
|
*/
|
|
909
1017
|
|
|
910
1018
|
/**
|
|
@@ -1196,4 +1304,4 @@ interface BuildAuthInfoFromBearerOptions extends McpBearerCustomerRefOptions {
|
|
|
1196
1304
|
}
|
|
1197
1305
|
declare function buildAuthInfoFromBearer(authorization?: string | null, options?: BuildAuthInfoFromBearerOptions): McpToolExtra['authInfo'] | null;
|
|
1198
1306
|
|
|
1199
|
-
export { type BootstrapCustomer, type BootstrapMerchant, type BootstrapPayload, type BootstrapPlan, type BootstrapProduct, type BuildAuthInfoFromBearerOptions, type BuildBootstrapPayloadFn, type BuildPayableHandlerContext, type BuildSolvaPayDescriptorsOptions, type BuildSolvaPayRequestOptions, type ContentBlock, type CreateBuildBootstrapPayloadOptions, type CustomerSnapshot, DEFAULT_OAUTH_PATHS, type IntentTool, MCP_TOOL_NAMES, type McpAdapterOptions, McpBearerAuthError, type McpBearerCustomerRefOptions, type McpToolExtra, type McpToolName, NARRATORS, type NarratorOutput, type NudgeSpec, type OAuthAuthorizationServerOptions, type OAuthBridgePaths, OPEN_TOOL_FOR_VIEW, type PayableHandler, type PaywallToolResult, type PaywallToolResultContext, type ResponseContext, type ResponseOptions, type ResponseResult, SOLVAPAY_DEFAULT_CSP, SOLVAPAY_MCP_VIEW_KINDS, SOLVAPAY_OVERVIEW_MARKDOWN, SOLVAPAY_OVERVIEW_MIME_TYPE, SOLVAPAY_OVERVIEW_URI, type SolvaPayCallToolResult, type SolvaPayDescriptorBundle, type SolvaPayDocsResourceDescriptor, type SolvaPayMcpCsp, type SolvaPayMcpViewKind, type SolvaPayMerchantBranding, type SolvaPayPromptDescriptor, type SolvaPayPromptResult, type SolvaPayResourceDescriptor, type SolvaPayToolAnnotations, type SolvaPayToolDescriptor, type SolvaPayToolIcon, type SolvaPayToolMode, TOOL_FOR_VIEW, VIEW_FOR_OPEN_TOOL, VIEW_FOR_TOOL, applyHideToolsByAudience, balanceSummary, buildAuthInfoFromBearer, buildPayableHandler, buildSolvaPayDescriptors, buildSolvaPayPrompts, buildSolvaPayRequest, createBuildBootstrapPayload, decodeJwtPayload, defaultGetCustomerRef, deriveIcons, enrichPurchase, extractBearerToken, getCustomerRefFromBearerAuthHeader, getCustomerRefFromJwtPayload, getOAuthAuthorizationServerResponse, getOAuthProtectedResourceResponse, mergeCsp, narrateActivatePlan, narrateManageAccount, narrateTopup, narrateUpgrade, narratedToolResult, parseMode, paywallToolResult, previewJson, resolveOAuthPaths, toolErrorResult, toolResult, uiPlaceholder, withoutTrailingSlash };
|
|
1307
|
+
export { type ApplyHideToolsByAudienceContext, type ApplyHideToolsByAudienceExtra, type ApplyHideToolsByAudienceOptions, type BootstrapCustomer, type BootstrapMerchant, type BootstrapPayload, type BootstrapPlan, type BootstrapProduct, type BuildAuthInfoFromBearerOptions, type BuildBootstrapPayloadFn, type BuildPayableHandlerContext, type BuildSolvaPayDescriptorsOptions, type BuildSolvaPayRequestOptions, type ContentBlock, type CreateBuildBootstrapPayloadOptions, type CustomerSnapshot, DEFAULT_OAUTH_PATHS, type HideToolsByAudienceBypass, type IntentTool, MCP_TOOL_NAMES, type McpAdapterOptions, McpBearerAuthError, type McpBearerCustomerRefOptions, type McpToolExtra, type McpToolName, NARRATORS, type NarratorOutput, type NudgeSpec, type OAuthAuthorizationServerOptions, type OAuthBridgePaths, OPEN_TOOL_FOR_VIEW, type PayableHandler, type PaywallToolResult, type PaywallToolResultContext, type ResponseContext, type ResponseOptions, type ResponseResult, SOLVAPAY_DEFAULT_CSP, SOLVAPAY_MCP_VIEW_KINDS, SOLVAPAY_OVERVIEW_MARKDOWN, SOLVAPAY_OVERVIEW_MIME_TYPE, SOLVAPAY_OVERVIEW_URI, type SolvaPayCallToolResult, type SolvaPayDescriptorBundle, type SolvaPayDocsResourceDescriptor, type SolvaPayMcpCsp, type SolvaPayMcpViewKind, type SolvaPayMerchantBranding, type SolvaPayPromptDescriptor, type SolvaPayPromptResult, type SolvaPayResourceDescriptor, type SolvaPayToolAnnotations, type SolvaPayToolDescriptor, type SolvaPayToolIcon, type SolvaPayToolMode, TOOL_FOR_VIEW, VIEW_FOR_OPEN_TOOL, VIEW_FOR_TOOL, applyHideToolsByAudience, balanceSummary, buildAuthInfoFromBearer, buildPayableHandler, buildSolvaPayDescriptors, buildSolvaPayPrompts, buildSolvaPayRequest, createBuildBootstrapPayload, decodeJwtPayload, defaultGetCustomerRef, defaultIsChatGptRequest, deriveIcons, enrichPurchase, extractBearerToken, getCustomerRefFromBearerAuthHeader, getCustomerRefFromJwtPayload, getOAuthAuthorizationServerResponse, getOAuthProtectedResourceResponse, mergeCsp, narrateActivatePlan, narrateManageAccount, narrateTopup, narrateUpgrade, narratedToolResult, parseMode, paywallToolResult, previewJson, resolveOAuthPaths, toolErrorResult, toolResult, uiPlaceholder, withoutTrailingSlash };
|
package/dist/index.d.ts
CHANGED
|
@@ -734,6 +734,12 @@ declare function parseMode(raw: unknown): SolvaPayToolMode;
|
|
|
734
734
|
*
|
|
735
735
|
* The narrator is picked by the `tool` name; unknown tools fall back
|
|
736
736
|
* to the JSON dump that `toolResult` produces today.
|
|
737
|
+
*
|
|
738
|
+
* Meta keys other than `ui` (notably `openai/widgetSessionId`, the
|
|
739
|
+
* ChatGPT MCP routing-bug workaround stamped by intent tools — see
|
|
740
|
+
* the descriptors.ts top-of-file comment) are preserved across all
|
|
741
|
+
* three modes. Don't strip additional `_meta` keys here without
|
|
742
|
+
* checking `descriptors.ts` first.
|
|
737
743
|
*/
|
|
738
744
|
declare function narratedToolResult(tool: IntentTool | string, data: BootstrapPayload, mode?: SolvaPayToolMode, baseMeta?: Record<string, unknown> | undefined): SolvaPayCallToolResult;
|
|
739
745
|
/**
|
|
@@ -861,7 +867,7 @@ declare const SOLVAPAY_DEFAULT_CSP: Required<SolvaPayMcpCsp>;
|
|
|
861
867
|
declare function mergeCsp(overrides: SolvaPayMcpCsp | undefined, apiBaseUrl?: string): Required<SolvaPayMcpCsp>;
|
|
862
868
|
|
|
863
869
|
/**
|
|
864
|
-
* `applyHideToolsByAudience(server, audiences)` — wraps the
|
|
870
|
+
* `applyHideToolsByAudience(server, audiences, options?)` — wraps the
|
|
865
871
|
* `tools/list` request handler on an `@modelcontextprotocol/sdk`
|
|
866
872
|
* `McpServer` so tool descriptors whose `_meta.audience` matches one
|
|
867
873
|
* of the supplied values are filtered out of the response.
|
|
@@ -869,21 +875,50 @@ declare function mergeCsp(overrides: SolvaPayMcpCsp | undefined, apiBaseUrl?: st
|
|
|
869
875
|
* The tools stay `enabled: true` on the server, so `tools/call` still
|
|
870
876
|
* reaches their handlers — this helper only affects the `tools/list`
|
|
871
877
|
* response shape. Use `['ui']` when deploying to a text-host MCP
|
|
872
|
-
* client (Claude Desktop, MCPJam, ChatGPT
|
|
878
|
+
* client (Claude Desktop, MCPJam, ChatGPT connector) that won't
|
|
873
879
|
* embed the SolvaPay iframe surface, while still allowing the iframe
|
|
874
880
|
* to invoke the hidden transport tools (`create_payment_intent` etc.)
|
|
875
881
|
* for server-side work.
|
|
876
882
|
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
882
|
-
* `
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
883
|
+
* # ChatGPT auto-bypass
|
|
884
|
+
*
|
|
885
|
+
* ChatGPT's Custom Connector gateway re-validates iframe-initiated
|
|
886
|
+
* `tools/call` against the cached `tools/list` catalog. A tool hidden
|
|
887
|
+
* from `tools/list` becomes uncallable from the embedded iframe and
|
|
888
|
+
* surfaces in the UI as `MCP error -32000: MCP Resource not found`.
|
|
889
|
+
*
|
|
890
|
+
* To keep the cleaner LLM-facing catalog on every other host while
|
|
891
|
+
* the iframe still works on ChatGPT, the default behaviour
|
|
892
|
+
* automatically returns the **full** unfiltered catalog when the
|
|
893
|
+
* incoming `tools/list` request originates from ChatGPT — detected by
|
|
894
|
+
* matching `request.headers['user-agent']` and the post-`initialize`
|
|
895
|
+
* `server.getClientVersion().name` against `/openai-mcp/i`. The first
|
|
896
|
+
* triggers on the discovery `tools/list` ChatGPT issues before
|
|
897
|
+
* `initialize` (so `getClientVersion()` is empty); the second is a
|
|
898
|
+
* defence-in-depth fallback for any future relay that strips the
|
|
899
|
+
* client UA.
|
|
900
|
+
*
|
|
901
|
+
* The User-Agent shape was confirmed live against ChatGPT's MCP
|
|
902
|
+
* runtime (`openai-mcp/1.0.0 (ChatGPT)` as of 2026-05). The pattern
|
|
903
|
+
* is intentionally broad so a UA bump to `openai-mcp/2.x` keeps
|
|
904
|
+
* working without code changes.
|
|
905
|
+
*
|
|
906
|
+
* Override the detection by passing `bypassWhen` — useful when a
|
|
907
|
+
* future iframe-capable host needs the same treatment, or when
|
|
908
|
+
* ChatGPT-served deployments want the LLM-narrow catalog regardless
|
|
909
|
+
* (`bypassWhen: () => false`).
|
|
910
|
+
*
|
|
911
|
+
* # Internal-map reach-in rationale
|
|
912
|
+
*
|
|
913
|
+
* `Protocol.setRequestHandler` contains an `assertCanSetRequestHandler`
|
|
914
|
+
* guard that fails when the SDK has already registered a handler for
|
|
915
|
+
* the method during registration — i.e. `tools/list` after any tool
|
|
916
|
+
* registration. The guard only fires through the public wrapper; the
|
|
917
|
+
* underlying `_requestHandlers` map accepts a replacement silently.
|
|
918
|
+
* Touching the map directly therefore sidesteps the guard without
|
|
919
|
+
* going through public API surface that could change in a minor SDK
|
|
920
|
+
* bump. The one piece of SDK-internal knowledge we live with until
|
|
921
|
+
* the SDK ships a first-class "replace handler" affordance.
|
|
887
922
|
*
|
|
888
923
|
* No-op when `audiences` is empty or falsy.
|
|
889
924
|
*
|
|
@@ -892,7 +927,58 @@ declare function mergeCsp(overrides: SolvaPayMcpCsp | undefined, apiBaseUrl?: st
|
|
|
892
927
|
* `@solvapay/mcp/fetch` (unified `createSolvaPayMcpFetch`) can apply
|
|
893
928
|
* the same filter without each re-implementing the reach-in.
|
|
894
929
|
*/
|
|
895
|
-
|
|
930
|
+
/**
|
|
931
|
+
* Headers shape exposed by the MCP SDK's `RequestHandlerExtra.requestInfo`.
|
|
932
|
+
* Mirrors `IsomorphicHeaders` from the SDK without taking a type dep.
|
|
933
|
+
*/
|
|
934
|
+
type IsomorphicHeaders = Record<string, string | string[] | undefined>;
|
|
935
|
+
/**
|
|
936
|
+
* Subset of `RequestHandlerExtra` (from `@modelcontextprotocol/sdk`)
|
|
937
|
+
* that the bypass predicate inspects. Typed loosely so we don't
|
|
938
|
+
* couple to the SDK's exact shape.
|
|
939
|
+
*/
|
|
940
|
+
interface ApplyHideToolsByAudienceExtra {
|
|
941
|
+
requestInfo?: {
|
|
942
|
+
headers?: IsomorphicHeaders;
|
|
943
|
+
} | undefined;
|
|
944
|
+
[key: string]: unknown;
|
|
945
|
+
}
|
|
946
|
+
interface ApplyHideToolsByAudienceContext {
|
|
947
|
+
/** The MCP server instance the filter is being applied to. */
|
|
948
|
+
server: unknown;
|
|
949
|
+
/**
|
|
950
|
+
* The `RequestHandlerExtra` the SDK passed to the wrapped
|
|
951
|
+
* `tools/list` handler. May be undefined for non-HTTP transports
|
|
952
|
+
* (e.g. stdio) where there's no request information.
|
|
953
|
+
*/
|
|
954
|
+
extra?: ApplyHideToolsByAudienceExtra;
|
|
955
|
+
}
|
|
956
|
+
type HideToolsByAudienceBypass = (ctx: ApplyHideToolsByAudienceContext) => boolean;
|
|
957
|
+
interface ApplyHideToolsByAudienceOptions {
|
|
958
|
+
/**
|
|
959
|
+
* When this predicate returns `true` for an incoming `tools/list`
|
|
960
|
+
* request, the audience filter is skipped and the full catalog is
|
|
961
|
+
* returned. Defaults to `defaultIsChatGptRequest` — see the file
|
|
962
|
+
* header for the rationale.
|
|
963
|
+
*
|
|
964
|
+
* Pass an integrator-supplied predicate to extend the bypass to
|
|
965
|
+
* another host (e.g. a future iframe-capable client), or `() =>
|
|
966
|
+
* false` to apply the filter unconditionally.
|
|
967
|
+
*/
|
|
968
|
+
bypassWhen?: HideToolsByAudienceBypass;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Default `bypassWhen` — returns true when the incoming request looks
|
|
972
|
+
* like it's coming from ChatGPT's MCP runtime. Two signals:
|
|
973
|
+
*
|
|
974
|
+
* 1. HTTP `User-Agent` header (works on the pre-`initialize`
|
|
975
|
+
* discovery `tools/list`, so we have a signal even before the
|
|
976
|
+
* SDK's `getClientVersion()` is populated).
|
|
977
|
+
* 2. `server.getClientVersion()?.name` (covers any host that relays
|
|
978
|
+
* ChatGPT requests without forwarding the upstream UA).
|
|
979
|
+
*/
|
|
980
|
+
declare function defaultIsChatGptRequest(ctx: ApplyHideToolsByAudienceContext): boolean;
|
|
981
|
+
declare function applyHideToolsByAudience(server: unknown, audiences: readonly string[] | undefined, options?: ApplyHideToolsByAudienceOptions): void;
|
|
896
982
|
|
|
897
983
|
/**
|
|
898
984
|
* `buildSolvaPayDescriptors(options)` — framework-neutral tool surface
|
|
@@ -905,6 +991,28 @@ declare function applyHideToolsByAudience(server: unknown, audiences: readonly s
|
|
|
905
991
|
* mechanical difference: instead of calling `registerAppTool(server, ...)`,
|
|
906
992
|
* we push `{ name, handler, ... }` onto a `tools[]` array the adapter
|
|
907
993
|
* iterates.
|
|
994
|
+
*
|
|
995
|
+
* ----
|
|
996
|
+
*
|
|
997
|
+
* `_meta["openai/widgetSessionId"]` workaround. Every intent-tool
|
|
998
|
+
* response stamps a freshly-minted UUID on `_meta["openai/widgetSessionId"]`.
|
|
999
|
+
* This is a low-risk forward-looking workaround for the ChatGPT MCP
|
|
1000
|
+
* connector's stale `link_<id>` routing bug, where the host returns
|
|
1001
|
+
* `-32000 MCP Resource not found` on the second `tools/call` of a
|
|
1002
|
+
* session even though the call never reaches the server. A fresh UUID
|
|
1003
|
+
* per invocation gives the host a routing key that changes every call,
|
|
1004
|
+
* which the OpenAI Apps SDK community thread reports unsticks the
|
|
1005
|
+
* failure mode.
|
|
1006
|
+
*
|
|
1007
|
+
* Sources:
|
|
1008
|
+
* - https://community.openai.com/t/connector-tool-calls-generating-fresh-mcp-session-each-invocation/1364975
|
|
1009
|
+
* - https://github.com/openai/openai-apps-sdk-examples/issues/165
|
|
1010
|
+
* - https://developers.openai.com/apps-sdk/reference/ (`_meta` payload)
|
|
1011
|
+
* - openai/openai-apps-sdk-examples shopping_cart_python uses the
|
|
1012
|
+
* same `meta["openai/widgetSessionId"]` shape.
|
|
1013
|
+
*
|
|
1014
|
+
* Removable once the upstream bug ships a fix; safe on any host that
|
|
1015
|
+
* doesn't consume the key.
|
|
908
1016
|
*/
|
|
909
1017
|
|
|
910
1018
|
/**
|
|
@@ -1196,4 +1304,4 @@ interface BuildAuthInfoFromBearerOptions extends McpBearerCustomerRefOptions {
|
|
|
1196
1304
|
}
|
|
1197
1305
|
declare function buildAuthInfoFromBearer(authorization?: string | null, options?: BuildAuthInfoFromBearerOptions): McpToolExtra['authInfo'] | null;
|
|
1198
1306
|
|
|
1199
|
-
export { type BootstrapCustomer, type BootstrapMerchant, type BootstrapPayload, type BootstrapPlan, type BootstrapProduct, type BuildAuthInfoFromBearerOptions, type BuildBootstrapPayloadFn, type BuildPayableHandlerContext, type BuildSolvaPayDescriptorsOptions, type BuildSolvaPayRequestOptions, type ContentBlock, type CreateBuildBootstrapPayloadOptions, type CustomerSnapshot, DEFAULT_OAUTH_PATHS, type IntentTool, MCP_TOOL_NAMES, type McpAdapterOptions, McpBearerAuthError, type McpBearerCustomerRefOptions, type McpToolExtra, type McpToolName, NARRATORS, type NarratorOutput, type NudgeSpec, type OAuthAuthorizationServerOptions, type OAuthBridgePaths, OPEN_TOOL_FOR_VIEW, type PayableHandler, type PaywallToolResult, type PaywallToolResultContext, type ResponseContext, type ResponseOptions, type ResponseResult, SOLVAPAY_DEFAULT_CSP, SOLVAPAY_MCP_VIEW_KINDS, SOLVAPAY_OVERVIEW_MARKDOWN, SOLVAPAY_OVERVIEW_MIME_TYPE, SOLVAPAY_OVERVIEW_URI, type SolvaPayCallToolResult, type SolvaPayDescriptorBundle, type SolvaPayDocsResourceDescriptor, type SolvaPayMcpCsp, type SolvaPayMcpViewKind, type SolvaPayMerchantBranding, type SolvaPayPromptDescriptor, type SolvaPayPromptResult, type SolvaPayResourceDescriptor, type SolvaPayToolAnnotations, type SolvaPayToolDescriptor, type SolvaPayToolIcon, type SolvaPayToolMode, TOOL_FOR_VIEW, VIEW_FOR_OPEN_TOOL, VIEW_FOR_TOOL, applyHideToolsByAudience, balanceSummary, buildAuthInfoFromBearer, buildPayableHandler, buildSolvaPayDescriptors, buildSolvaPayPrompts, buildSolvaPayRequest, createBuildBootstrapPayload, decodeJwtPayload, defaultGetCustomerRef, deriveIcons, enrichPurchase, extractBearerToken, getCustomerRefFromBearerAuthHeader, getCustomerRefFromJwtPayload, getOAuthAuthorizationServerResponse, getOAuthProtectedResourceResponse, mergeCsp, narrateActivatePlan, narrateManageAccount, narrateTopup, narrateUpgrade, narratedToolResult, parseMode, paywallToolResult, previewJson, resolveOAuthPaths, toolErrorResult, toolResult, uiPlaceholder, withoutTrailingSlash };
|
|
1307
|
+
export { type ApplyHideToolsByAudienceContext, type ApplyHideToolsByAudienceExtra, type ApplyHideToolsByAudienceOptions, type BootstrapCustomer, type BootstrapMerchant, type BootstrapPayload, type BootstrapPlan, type BootstrapProduct, type BuildAuthInfoFromBearerOptions, type BuildBootstrapPayloadFn, type BuildPayableHandlerContext, type BuildSolvaPayDescriptorsOptions, type BuildSolvaPayRequestOptions, type ContentBlock, type CreateBuildBootstrapPayloadOptions, type CustomerSnapshot, DEFAULT_OAUTH_PATHS, type HideToolsByAudienceBypass, type IntentTool, MCP_TOOL_NAMES, type McpAdapterOptions, McpBearerAuthError, type McpBearerCustomerRefOptions, type McpToolExtra, type McpToolName, NARRATORS, type NarratorOutput, type NudgeSpec, type OAuthAuthorizationServerOptions, type OAuthBridgePaths, OPEN_TOOL_FOR_VIEW, type PayableHandler, type PaywallToolResult, type PaywallToolResultContext, type ResponseContext, type ResponseOptions, type ResponseResult, SOLVAPAY_DEFAULT_CSP, SOLVAPAY_MCP_VIEW_KINDS, SOLVAPAY_OVERVIEW_MARKDOWN, SOLVAPAY_OVERVIEW_MIME_TYPE, SOLVAPAY_OVERVIEW_URI, type SolvaPayCallToolResult, type SolvaPayDescriptorBundle, type SolvaPayDocsResourceDescriptor, type SolvaPayMcpCsp, type SolvaPayMcpViewKind, type SolvaPayMerchantBranding, type SolvaPayPromptDescriptor, type SolvaPayPromptResult, type SolvaPayResourceDescriptor, type SolvaPayToolAnnotations, type SolvaPayToolDescriptor, type SolvaPayToolIcon, type SolvaPayToolMode, TOOL_FOR_VIEW, VIEW_FOR_OPEN_TOOL, VIEW_FOR_TOOL, applyHideToolsByAudience, balanceSummary, buildAuthInfoFromBearer, buildPayableHandler, buildSolvaPayDescriptors, buildSolvaPayPrompts, buildSolvaPayRequest, createBuildBootstrapPayload, decodeJwtPayload, defaultGetCustomerRef, defaultIsChatGptRequest, deriveIcons, enrichPurchase, extractBearerToken, getCustomerRefFromBearerAuthHeader, getCustomerRefFromJwtPayload, getOAuthAuthorizationServerResponse, getOAuthProtectedResourceResponse, mergeCsp, narrateActivatePlan, narrateManageAccount, narrateTopup, narrateUpgrade, narratedToolResult, parseMode, paywallToolResult, previewJson, resolveOAuthPaths, toolErrorResult, toolResult, uiPlaceholder, withoutTrailingSlash };
|
package/dist/index.js
CHANGED
|
@@ -424,9 +424,24 @@ function mergeCsp(overrides, apiBaseUrl) {
|
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
// src/hideToolsByAudience.ts
|
|
427
|
-
|
|
427
|
+
var CHATGPT_CLIENT_RE = /openai-mcp/i;
|
|
428
|
+
function readHeader(headers, name) {
|
|
429
|
+
if (!headers) return void 0;
|
|
430
|
+
const raw = headers[name] ?? headers[name.toLowerCase()];
|
|
431
|
+
if (Array.isArray(raw)) return raw[0];
|
|
432
|
+
return typeof raw === "string" ? raw : void 0;
|
|
433
|
+
}
|
|
434
|
+
function defaultIsChatGptRequest(ctx) {
|
|
435
|
+
const ua = readHeader(ctx.extra?.requestInfo?.headers, "user-agent");
|
|
436
|
+
if (ua && CHATGPT_CLIENT_RE.test(ua)) return true;
|
|
437
|
+
const clientVersion = ctx.server?.server?.getClientVersion?.();
|
|
438
|
+
const clientName = typeof clientVersion?.name === "string" ? clientVersion.name : void 0;
|
|
439
|
+
return clientName !== void 0 && CHATGPT_CLIENT_RE.test(clientName);
|
|
440
|
+
}
|
|
441
|
+
function applyHideToolsByAudience(server, audiences, options = {}) {
|
|
428
442
|
if (!audiences || audiences.length === 0) return;
|
|
429
443
|
const hidden = new Set(audiences);
|
|
444
|
+
const bypassWhen = options.bypassWhen ?? defaultIsChatGptRequest;
|
|
430
445
|
const inner = server.server;
|
|
431
446
|
if (!inner || typeof inner !== "object" || !(inner._requestHandlers instanceof Map)) {
|
|
432
447
|
return;
|
|
@@ -434,8 +449,26 @@ function applyHideToolsByAudience(server, audiences) {
|
|
|
434
449
|
const handlers = inner._requestHandlers;
|
|
435
450
|
const original = handlers.get("tools/list");
|
|
436
451
|
if (!original) return;
|
|
452
|
+
const warned = /* @__PURE__ */ new Set();
|
|
437
453
|
handlers.set("tools/list", async (req, extra) => {
|
|
438
454
|
const res = await original(req, extra);
|
|
455
|
+
if (bypassWhen({
|
|
456
|
+
server,
|
|
457
|
+
extra
|
|
458
|
+
})) {
|
|
459
|
+
const ua = readHeader(
|
|
460
|
+
extra?.requestInfo?.headers,
|
|
461
|
+
"user-agent"
|
|
462
|
+
);
|
|
463
|
+
const context = ua ? `ua=${ua}` : "no user-agent";
|
|
464
|
+
if (!warned.has(context)) {
|
|
465
|
+
warned.add(context);
|
|
466
|
+
console.warn(
|
|
467
|
+
`[solvapay/mcp] hideToolsByAudience filter bypassed (${context}); returning full tools/list catalog.`
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
return res;
|
|
471
|
+
}
|
|
439
472
|
const tools = Array.isArray(res?.tools) ? res.tools : [];
|
|
440
473
|
return {
|
|
441
474
|
...res,
|
|
@@ -706,7 +739,10 @@ function buildSolvaPayDescriptors(options) {
|
|
|
706
739
|
handler: async (args, extra) => trace(name, args, extra, async () => {
|
|
707
740
|
const mode = parseMode(args.mode);
|
|
708
741
|
const data = await buildBootstrapPayload(view, extra);
|
|
709
|
-
return narratedToolResult(name, data, mode,
|
|
742
|
+
return narratedToolResult(name, data, mode, {
|
|
743
|
+
...toolMeta,
|
|
744
|
+
"openai/widgetSessionId": crypto.randomUUID()
|
|
745
|
+
});
|
|
710
746
|
})
|
|
711
747
|
});
|
|
712
748
|
};
|
|
@@ -907,7 +943,7 @@ function buildSolvaPayDescriptors(options) {
|
|
|
907
943
|
MCP_TOOL_NAMES.activatePlan,
|
|
908
944
|
await buildBootstrapPayload("checkout", extra),
|
|
909
945
|
mode,
|
|
910
|
-
toolMeta
|
|
946
|
+
{ ...toolMeta, "openai/widgetSessionId": crypto.randomUUID() }
|
|
911
947
|
);
|
|
912
948
|
}
|
|
913
949
|
const auth = requireCustomerRef(extra);
|
|
@@ -1316,6 +1352,7 @@ export {
|
|
|
1316
1352
|
createBuildBootstrapPayload,
|
|
1317
1353
|
decodeJwtPayload,
|
|
1318
1354
|
defaultGetCustomerRef,
|
|
1355
|
+
defaultIsChatGptRequest,
|
|
1319
1356
|
deriveIcons,
|
|
1320
1357
|
enrichPurchase,
|
|
1321
1358
|
extractBearerToken,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solvapay/mcp-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Framework-neutral MCP contracts for the SolvaPay SDK (tool names, descriptors, payable handler, paywall meta, CSP, bootstrap payload, OAuth discovery JSON builders, bearer/JWT helpers).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|