@apiosk/mcp 1.3.0 → 1.7.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/README.md +135 -8
- package/docs/sepa-rail.md +13 -13
- package/dxt.json +7 -4
- package/logo-optimized-light.png +0 -0
- package/package.json +13 -8
- package/server.json +78 -4
- package/server.mjs +213 -11
- package/src/assets/wallet-accounts.mjs +20513 -0
- package/src/assets/walletconnect-provider.mjs +6319 -0
- package/src/create-server.mjs +47 -2
- package/src/discovery.mjs +929 -0
- package/src/external-fetch.mjs +203 -0
- package/src/hosted-payment.mjs +552 -0
- package/src/hosted-wallets.mjs +530 -0
- package/src/oauth.mjs +2011 -225
- package/src/observability.mjs +194 -0
- package/src/payment-guidance.mjs +15 -24
- package/src/publisher.mjs +1288 -0
- package/src/result-canvas.mjs +16 -0
- package/src/runtime.mjs +621 -30
- package/src/settlement-disclosure.mjs +26 -0
- package/src/source-registry.mjs +215 -0
- package/src/x402-inspect.mjs +361 -0
package/src/runtime.mjs
CHANGED
|
@@ -17,10 +17,130 @@ import {
|
|
|
17
17
|
saveLocalApioskConfig,
|
|
18
18
|
} from "./local-config.mjs";
|
|
19
19
|
import { createLocalWalletStore } from "./wallet-store.mjs";
|
|
20
|
+
import { logToolCall } from "./observability.mjs";
|
|
21
|
+
import {
|
|
22
|
+
hostedCreateWalletToken,
|
|
23
|
+
hostedCreateWalletUnavailable,
|
|
24
|
+
hostedDeleteWallet,
|
|
25
|
+
hostedDeleteWalletToken,
|
|
26
|
+
hostedListWallets,
|
|
27
|
+
hostedListWalletTokens,
|
|
28
|
+
hostedUpdateWallet,
|
|
29
|
+
hostedUpdateWalletToken,
|
|
30
|
+
hostedWalletActivity,
|
|
31
|
+
} from "./hosted-wallets.mjs";
|
|
32
|
+
import {
|
|
33
|
+
PUBLISHER_TOOLS,
|
|
34
|
+
handlePublisherTool,
|
|
35
|
+
isPublisherTool,
|
|
36
|
+
} from "./publisher.mjs";
|
|
37
|
+
import { DISCOVER_TOOL, runDiscover } from "./discovery.mjs";
|
|
38
|
+
import { searchKnownSources } from "./source-registry.mjs";
|
|
39
|
+
import { INSPECT_TOOL, runInspect } from "./x402-inspect.mjs";
|
|
40
|
+
import { FETCH_PAID_TOOL, runFetchPaid } from "./external-fetch.mjs";
|
|
20
41
|
|
|
21
42
|
const DEFAULT_LIMIT = 25;
|
|
22
43
|
const CACHE_TTL_MS = 60_000;
|
|
23
44
|
const DEFAULT_GATEWAY_BASE_URL = "https://gateway.apiosk.com";
|
|
45
|
+
const HOSTED_OAUTH_SCOPE = "mcp:tools";
|
|
46
|
+
|
|
47
|
+
// Permissive default output schema advertised for every tool that does not
|
|
48
|
+
// declare its own. MCP clients (ChatGPT, Claude, …) flag tools with no
|
|
49
|
+
// outputSchema as "output schema recommended"; declaring one clears that hint.
|
|
50
|
+
// Apiosk tool results are always returned as a JSON object in structuredContent
|
|
51
|
+
// (see content()), so an open object schema both documents that contract and
|
|
52
|
+
// validates every success payload without risking false rejections on the
|
|
53
|
+
// varied per-tool result shapes. Individual tools can still ship a tighter
|
|
54
|
+
// schema (e.g. dynamic listings via listing_metadata.mcp_tool.outputSchema),
|
|
55
|
+
// which is preserved as-is.
|
|
56
|
+
const DEFAULT_TOOL_OUTPUT_SCHEMA = {
|
|
57
|
+
type: "object",
|
|
58
|
+
additionalProperties: true,
|
|
59
|
+
description:
|
|
60
|
+
"Structured JSON result of the tool call. Mirrors the human-readable text content; the exact fields depend on the tool (an `error` field is present when the call fails).",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Behaviour hints (MCP tool annotations). Clients like ChatGPT default an
|
|
64
|
+
// un-annotated tool to the most cautious badges — "destructive" and
|
|
65
|
+
// "open world" — so every write tool that ships no annotations reads as
|
|
66
|
+
// dangerous. These presets make the badges accurate: wallet/account/keystore
|
|
67
|
+
// management is a closed domain (the user's own Apiosk account), publishing
|
|
68
|
+
// wires up an external upstream (open world), and deletes are destructive.
|
|
69
|
+
const READ_ONLY_ANNOTATIONS = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
70
|
+
const CREATE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
71
|
+
const UPDATE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
72
|
+
const DELETE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false };
|
|
73
|
+
const SETUP_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
|
|
74
|
+
const PUBLISH_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
|
|
75
|
+
const PUBLISH_UPDATE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
76
|
+
const PUBLISH_DELETE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true };
|
|
77
|
+
// Dynamic per-listing tools call arbitrary paid third-party APIs and spend
|
|
78
|
+
// USDC, so an unspecified listing gets the same hints as apiosk_execute.
|
|
79
|
+
const DYNAMIC_EXECUTE_ANNOTATIONS = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true };
|
|
80
|
+
|
|
81
|
+
// Static tools whose literals don't already declare `annotations`. Discovery
|
|
82
|
+
// and publisher (sk_live_) tools annotate themselves inline and are absent here.
|
|
83
|
+
const TOOL_ANNOTATIONS = {
|
|
84
|
+
// Managed (dashboard) wallet + account tools — closed Apiosk-account domain.
|
|
85
|
+
apiosk_list_wallets: READ_ONLY_ANNOTATIONS,
|
|
86
|
+
apiosk_get_wallet_activity: READ_ONLY_ANNOTATIONS,
|
|
87
|
+
apiosk_list_wallet_api_keys: READ_ONLY_ANNOTATIONS,
|
|
88
|
+
apiosk_show_wallet_funding: READ_ONLY_ANNOTATIONS,
|
|
89
|
+
apiosk_create_wallet: CREATE_ANNOTATIONS,
|
|
90
|
+
apiosk_create_wallet_connect_string: CREATE_ANNOTATIONS,
|
|
91
|
+
apiosk_create_wallet_api_key: CREATE_ANNOTATIONS,
|
|
92
|
+
apiosk_create_account: CREATE_ANNOTATIONS,
|
|
93
|
+
apiosk_update_wallet: UPDATE_ANNOTATIONS,
|
|
94
|
+
apiosk_update_wallet_api_key: UPDATE_ANNOTATIONS,
|
|
95
|
+
apiosk_sign_in: UPDATE_ANNOTATIONS,
|
|
96
|
+
apiosk_delete_wallet: DELETE_ANNOTATIONS,
|
|
97
|
+
apiosk_delete_wallet_api_key: DELETE_ANNOTATIONS,
|
|
98
|
+
// Local (stdio) wallet keystore tools.
|
|
99
|
+
apiosk_wallet_list: READ_ONLY_ANNOTATIONS,
|
|
100
|
+
apiosk_wallet_reveal_secret: READ_ONLY_ANNOTATIONS,
|
|
101
|
+
apiosk_configure: READ_ONLY_ANNOTATIONS,
|
|
102
|
+
apiosk_wallet_create: CREATE_ANNOTATIONS,
|
|
103
|
+
apiosk_wallet_select: UPDATE_ANNOTATIONS,
|
|
104
|
+
apiosk_wallet_update: UPDATE_ANNOTATIONS,
|
|
105
|
+
apiosk_wallet_save_secret: UPDATE_ANNOTATIONS,
|
|
106
|
+
apiosk_wallet_delete: DELETE_ANNOTATIONS,
|
|
107
|
+
apiosk_get_started: SETUP_ANNOTATIONS,
|
|
108
|
+
// Wallet-signed publishing — registers an external upstream (open world).
|
|
109
|
+
apiosk_list_my_apis: READ_ONLY_ANNOTATIONS,
|
|
110
|
+
apiosk_publish_api: PUBLISH_ANNOTATIONS,
|
|
111
|
+
apiosk_update_api: PUBLISH_UPDATE_ANNOTATIONS,
|
|
112
|
+
apiosk_delete_api: PUBLISH_DELETE_ANNOTATIONS,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Applied at the single list-emission boundary (runtime.listTools): fills in a
|
|
116
|
+
// default output schema and behaviour annotations for any tool that doesn't
|
|
117
|
+
// already declare its own, so clients stop flagging "output schema recommended"
|
|
118
|
+
// and render accurate read-only/destructive/open-world badges.
|
|
119
|
+
function normalizeToolForClient(tool, { hosted = false, protectedTool = false } = {}) {
|
|
120
|
+
if (!tool || typeof tool !== "object") return tool;
|
|
121
|
+
let next = tool;
|
|
122
|
+
if (!next.outputSchema) {
|
|
123
|
+
next = { ...next, outputSchema: DEFAULT_TOOL_OUTPUT_SCHEMA };
|
|
124
|
+
}
|
|
125
|
+
if (!next.annotations && TOOL_ANNOTATIONS[next.name]) {
|
|
126
|
+
next = { ...next, annotations: TOOL_ANNOTATIONS[next.name] };
|
|
127
|
+
}
|
|
128
|
+
if (hosted && !next.securitySchemes) {
|
|
129
|
+
const securitySchemes = protectedTool
|
|
130
|
+
? [{ type: "oauth2", scopes: [HOSTED_OAUTH_SCOPE] }]
|
|
131
|
+
: [{ type: "noauth" }];
|
|
132
|
+
next = {
|
|
133
|
+
...next,
|
|
134
|
+
securitySchemes,
|
|
135
|
+
_meta: {
|
|
136
|
+
...(next._meta || {}),
|
|
137
|
+
// Back-compat mirror required by older ChatGPT connector clients.
|
|
138
|
+
securitySchemes,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return next;
|
|
143
|
+
}
|
|
24
144
|
|
|
25
145
|
const DASHBOARD_WALLET_TOOLS = [
|
|
26
146
|
{
|
|
@@ -502,7 +622,7 @@ const PUBLISH_TOOLS = [
|
|
|
502
622
|
|
|
503
623
|
const HELP_TOOL = {
|
|
504
624
|
name: "apiosk_help",
|
|
505
|
-
description: "Explain what Apiosk MCP is, how to connect it, how auth and x402 payments work,
|
|
625
|
+
description: "Explain what Apiosk MCP is, how to connect it, how auth and USDC/x402 payments work, and the recommended workflow for discovery, wallets, and publishing.",
|
|
506
626
|
annotations: {
|
|
507
627
|
readOnlyHint: true,
|
|
508
628
|
openWorldHint: false,
|
|
@@ -513,8 +633,8 @@ const HELP_TOOL = {
|
|
|
513
633
|
properties: {
|
|
514
634
|
topic: {
|
|
515
635
|
type: "string",
|
|
516
|
-
enum: ["overview", "setup", "auth", "workflow", "payments", "rails", "wallets", "publish", "configure"],
|
|
517
|
-
description: "Optional help topic. Defaults to overview. Use '
|
|
636
|
+
enum: ["overview", "setup", "auth", "workflow", "discovery", "payments", "rails", "wallets", "publish", "configure"],
|
|
637
|
+
description: "Optional help topic. Defaults to overview. Use 'discovery' to learn which live sources apiosk_discover searches (Apiosk catalog + Coinbase Bazaar + well-known); use 'rails' for how USDC/x402 settlement works.",
|
|
518
638
|
},
|
|
519
639
|
},
|
|
520
640
|
},
|
|
@@ -559,7 +679,7 @@ const EXPLORE_TOOL = {
|
|
|
559
679
|
|
|
560
680
|
const SEARCH_TOOL = {
|
|
561
681
|
name: "apiosk_search",
|
|
562
|
-
description: "Search and browse the Apiosk catalog.
|
|
682
|
+
description: "Search and browse the Apiosk catalog by capability, price, or category. For browsing/filtering the catalog. When the goal is to fulfil a user request with real paid data ('get me the live X'), prefer apiosk_discover, which decomposes the need and ranks the best endpoints across sources.",
|
|
563
683
|
annotations: {
|
|
564
684
|
readOnlyHint: true,
|
|
565
685
|
openWorldHint: false,
|
|
@@ -639,6 +759,12 @@ const EXECUTE_TOOL = {
|
|
|
639
759
|
openWorldHint: true,
|
|
640
760
|
destructiveHint: true,
|
|
641
761
|
},
|
|
762
|
+
_meta: {
|
|
763
|
+
"openai/outputTemplate": "ui://apiosk/result-canvas.html",
|
|
764
|
+
"openai/toolInvocation/invoking": "Fetching and paying for data…",
|
|
765
|
+
"openai/toolInvocation/invoked": "Paid data received",
|
|
766
|
+
ui: { resourceUri: "ui://apiosk/result-canvas.html" },
|
|
767
|
+
},
|
|
642
768
|
inputSchema: {
|
|
643
769
|
type: "object",
|
|
644
770
|
required: ["slug"],
|
|
@@ -688,7 +814,7 @@ const HEALTH_TOOL = {
|
|
|
688
814
|
const PAYMENT_GUIDE_TOOL = {
|
|
689
815
|
name: "apiosk_payment_guide",
|
|
690
816
|
description:
|
|
691
|
-
"Explain how to pay through the Apiosk gateway. Returns a buyer guide (how an agent settles a paid API call over USDC/x402
|
|
817
|
+
"Explain how to pay through the Apiosk gateway. Returns a buyer guide (how an agent settles a paid API call over USDC/x402, tailored to the current auth) and a provider guide (how to publish an API and get paid). Pass slug to scope buyer guidance to one listing, or role to pick a side.",
|
|
692
818
|
annotations: {
|
|
693
819
|
readOnlyHint: true,
|
|
694
820
|
openWorldHint: false,
|
|
@@ -715,15 +841,21 @@ const DISCOVERY_TOOLS = [
|
|
|
715
841
|
PAYMENT_GUIDE_TOOL,
|
|
716
842
|
EXPLORE_TOOL,
|
|
717
843
|
SEARCH_TOOL,
|
|
844
|
+
DISCOVER_TOOL,
|
|
845
|
+
INSPECT_TOOL,
|
|
718
846
|
GET_API_TOOL,
|
|
719
847
|
EXECUTE_TOOL,
|
|
720
848
|
];
|
|
721
849
|
// Discovery + payment guidance available to every remote buyer, even before
|
|
722
|
-
// they authorize (these tools are public / read-only).
|
|
850
|
+
// they authorize (these tools are public / read-only). apiosk_discover and
|
|
851
|
+
// apiosk_inspect_x402 are read-only too — they find candidate x402 endpoints and
|
|
852
|
+
// read their 402 terms without spending, so buyers can plan a purchase pre-auth.
|
|
723
853
|
const HOSTED_DISCOVERY_TOOLS = [
|
|
724
854
|
HELP_TOOL,
|
|
725
855
|
PAYMENT_GUIDE_TOOL,
|
|
726
856
|
SEARCH_TOOL,
|
|
857
|
+
DISCOVER_TOOL,
|
|
858
|
+
INSPECT_TOOL,
|
|
727
859
|
EXPLORE_TOOL,
|
|
728
860
|
GET_API_TOOL,
|
|
729
861
|
METADATA_TOOL,
|
|
@@ -734,18 +866,61 @@ const HOSTED_DISCOVERY_TOOLS = [
|
|
|
734
866
|
// Managed buyer tools that work over request-scoped dashboard auth on the
|
|
735
867
|
// hosted endpoint: prepaid credits + full managed agent-wallet CRUD. These are
|
|
736
868
|
// protected (the OAuth layer requires authorization before they run).
|
|
737
|
-
// apiosk_show_wallet_funding is intentionally excluded
|
|
869
|
+
// apiosk_show_wallet_funding is intentionally excluded, it resolves a local /
|
|
738
870
|
// env wallet, which does not exist on the hosted surface.
|
|
739
871
|
const HOSTED_MANAGED_TOOLS = [
|
|
740
|
-
...REMOTE_CREDITS_TOOLS,
|
|
741
872
|
...DASHBOARD_WALLET_TOOLS.filter((tool) => tool.name !== "apiosk_show_wallet_funding"),
|
|
742
873
|
];
|
|
743
874
|
|
|
744
875
|
// The hosted/remote surface is fully capable: discovery, payment guidance,
|
|
745
876
|
// generic + dynamic per-API execution, credits, and managed-wallet management.
|
|
746
|
-
//
|
|
747
|
-
// key the hosted server never
|
|
748
|
-
|
|
877
|
+
// Wallet-signed publishing (apiosk_publish_api & co.) stays local/portal-only
|
|
878
|
+
// because it requires a client-side signing key the hosted server never
|
|
879
|
+
// holds; the x402 publisher tools work hosted because they authenticate with
|
|
880
|
+
// a provider token (sk_live_…) instead of a wallet.
|
|
881
|
+
// External-payment buyer tool: pay an x402 endpoint the gateway does NOT host,
|
|
882
|
+
// from the connected managed wallet, via the gateway payer proxy. Protected
|
|
883
|
+
// (spends USDC) and available in every mode that can present a connect token.
|
|
884
|
+
const EXTERNAL_PAY_TOOLS = [FETCH_PAID_TOOL];
|
|
885
|
+
|
|
886
|
+
const HOSTED_REMOTE_TOOLS = [
|
|
887
|
+
...HOSTED_DISCOVERY_TOOLS,
|
|
888
|
+
...HOSTED_MANAGED_TOOLS,
|
|
889
|
+
...EXTERNAL_PAY_TOOLS,
|
|
890
|
+
...PUBLISHER_TOOLS,
|
|
891
|
+
];
|
|
892
|
+
|
|
893
|
+
// Lean BUYER surface for the hosted connector. A buyer opening the connector in
|
|
894
|
+
// ChatGPT/Claude should see the agentic flow + one wallet view — not 28 tools.
|
|
895
|
+
// Ordered flow-first. The advanced wallet CRUD (create/update/delete wallet +
|
|
896
|
+
// api-keys), explore/metadata/health, and provider publishing are dropped from
|
|
897
|
+
// the default list. Nothing is lost: every hidden tool is still dispatchable by
|
|
898
|
+
// name (callTool + isToolProtected are unchanged) and the full set is one env
|
|
899
|
+
// flag away (APIOSK_MCP_FULL_TOOLS=true). apiosk_list_wallets stays so buyers can
|
|
900
|
+
// see their wallet address, spend limits, and funding.
|
|
901
|
+
const HOSTED_BUYER_TOOLS = [
|
|
902
|
+
HELP_TOOL,
|
|
903
|
+
DISCOVER_TOOL,
|
|
904
|
+
EXPLORE_TOOL,
|
|
905
|
+
INSPECT_TOOL,
|
|
906
|
+
EXECUTE_TOOL,
|
|
907
|
+
FETCH_PAID_TOOL,
|
|
908
|
+
GET_API_TOOL,
|
|
909
|
+
SEARCH_TOOL,
|
|
910
|
+
PAYMENT_GUIDE_TOOL,
|
|
911
|
+
DASHBOARD_WALLET_TOOLS.find((tool) => tool.name === "apiosk_list_wallets"),
|
|
912
|
+
].filter(Boolean);
|
|
913
|
+
|
|
914
|
+
// PROVIDER surface (caller authenticated with a sk_live_ provider key): a focused
|
|
915
|
+
// publishing toolkit. The managed-wallet CRUD needs a dashboard JWT a provider
|
|
916
|
+
// key does not carry, so those tools would fail anyway — they're excluded here.
|
|
917
|
+
const HOSTED_PROVIDER_TOOLS = [
|
|
918
|
+
HELP_TOOL,
|
|
919
|
+
PAYMENT_GUIDE_TOOL,
|
|
920
|
+
SEARCH_TOOL,
|
|
921
|
+
GET_API_TOOL,
|
|
922
|
+
...PUBLISHER_TOOLS,
|
|
923
|
+
];
|
|
749
924
|
|
|
750
925
|
const ALL_STATIC_TOOLS = [
|
|
751
926
|
...DISCOVERY_TOOLS,
|
|
@@ -754,15 +929,28 @@ const ALL_STATIC_TOOLS = [
|
|
|
754
929
|
...LOCAL_WALLET_TOOLS,
|
|
755
930
|
...DASHBOARD_WALLET_TOOLS,
|
|
756
931
|
...PUBLISH_TOOLS,
|
|
932
|
+
...PUBLISHER_TOOLS,
|
|
757
933
|
];
|
|
758
934
|
|
|
935
|
+
// These tools read or mutate client-machine state (local files, local signing
|
|
936
|
+
// keys, or a local dashboard session). They must never execute in the hosted
|
|
937
|
+
// multi-tenant server, even if a stale MCP client cached an older tool list or
|
|
938
|
+
// the deployment accidentally carries APIOSK_ENABLE_LOCAL_WALLETS=true.
|
|
939
|
+
const LOCAL_ONLY_TOOL_NAMES = new Set([
|
|
940
|
+
...LOCAL_ACCOUNT_AND_CREDITS_TOOLS.map((tool) => tool.name),
|
|
941
|
+
...LOCAL_WALLET_TOOLS.map((tool) => tool.name),
|
|
942
|
+
...PUBLISH_TOOLS.map((tool) => tool.name),
|
|
943
|
+
]);
|
|
944
|
+
|
|
759
945
|
const PUBLIC_STATIC_TOOL_NAMES = new Set(
|
|
760
946
|
DISCOVERY_TOOLS.map((tool) => tool.name).filter((name) => name !== "apiosk_execute")
|
|
761
947
|
);
|
|
762
948
|
const REMOTE_PROTECTED_STATIC_TOOL_NAMES = new Set([
|
|
763
949
|
"apiosk_execute",
|
|
950
|
+
...EXTERNAL_PAY_TOOLS.map((tool) => tool.name),
|
|
764
951
|
...REMOTE_CREDITS_TOOLS.map((tool) => tool.name),
|
|
765
952
|
...DASHBOARD_WALLET_TOOLS.map((tool) => tool.name),
|
|
953
|
+
...PUBLISHER_TOOLS.map((tool) => tool.name),
|
|
766
954
|
]);
|
|
767
955
|
|
|
768
956
|
function trimString(value) {
|
|
@@ -786,7 +974,7 @@ function sanitizeToolName(name, fallback) {
|
|
|
786
974
|
* active but unverified -> pending review, active + verified -> live.
|
|
787
975
|
*
|
|
788
976
|
* NOTE: the public catalog (GET /v1/apis) hard-filters to active AND verified
|
|
789
|
-
* rows, so entries built from it are always "live"
|
|
977
|
+
* rows, so entries built from it are always "live", the field documents that
|
|
790
978
|
* invariant for catalog readers. The pending/disabled branches are reached when
|
|
791
979
|
* this runs over a richer object (e.g. a get_api_detail response, which returns
|
|
792
980
|
* the real active/verified flags).
|
|
@@ -821,9 +1009,33 @@ function buildCatalogEntry(api, toolName) {
|
|
|
821
1009
|
};
|
|
822
1010
|
}
|
|
823
1011
|
|
|
1012
|
+
function normalizeCatalogToken(value) {
|
|
1013
|
+
return String(value || "")
|
|
1014
|
+
.trim()
|
|
1015
|
+
.toLowerCase()
|
|
1016
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
1017
|
+
.replace(/^-+|-+$/g, "");
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function isWeatherLikeSlug(slug) {
|
|
1021
|
+
return /weather|meteo|forecast/.test(normalizeCatalogToken(slug));
|
|
1022
|
+
}
|
|
1023
|
+
|
|
824
1024
|
function buildDynamicTools(catalog, reservedTools) {
|
|
825
1025
|
const tools = [];
|
|
826
1026
|
const toolIndex = new Map();
|
|
1027
|
+
|
|
1028
|
+
// Per-API "dynamic" tools surface one MCP tool per gateway listing, which
|
|
1029
|
+
// DUPLICATES every paid API. Exposing them also lets the provider-portal MCP
|
|
1030
|
+
// import create separate /tools/<slug> gateway endpoints that undercut (or
|
|
1031
|
+
// give away for free) the real API listings. Agents must instead discover via
|
|
1032
|
+
// apiosk_search / apiosk_explore and call any API through apiosk_execute,
|
|
1033
|
+
// which settles the real per-call price. So these are OFF by default; set
|
|
1034
|
+
// APIOSK_MCP_DYNAMIC_TOOLS=true to opt back in.
|
|
1035
|
+
if (process.env.APIOSK_MCP_DYNAMIC_TOOLS !== "true") {
|
|
1036
|
+
return { tools, toolIndex };
|
|
1037
|
+
}
|
|
1038
|
+
|
|
827
1039
|
const usedNames = new Set(reservedTools.map((tool) => tool.name));
|
|
828
1040
|
|
|
829
1041
|
for (const api of catalog) {
|
|
@@ -860,7 +1072,16 @@ function buildDynamicTools(catalog, reservedTools) {
|
|
|
860
1072
|
type: "object",
|
|
861
1073
|
additionalProperties: true,
|
|
862
1074
|
},
|
|
863
|
-
|
|
1075
|
+
outputSchema:
|
|
1076
|
+
api.listing_metadata?.mcp_tool?.outputSchema || DEFAULT_TOOL_OUTPUT_SCHEMA,
|
|
1077
|
+
annotations:
|
|
1078
|
+
api.listing_metadata?.mcp_tool?.annotations || DYNAMIC_EXECUTE_ANNOTATIONS,
|
|
1079
|
+
_meta: {
|
|
1080
|
+
"openai/outputTemplate": "ui://apiosk/result-canvas.html",
|
|
1081
|
+
"openai/toolInvocation/invoking": `Fetching ${api.name || api.slug}…`,
|
|
1082
|
+
"openai/toolInvocation/invoked": `${api.name || api.slug} received`,
|
|
1083
|
+
ui: { resourceUri: "ui://apiosk/result-canvas.html" },
|
|
1084
|
+
},
|
|
864
1085
|
});
|
|
865
1086
|
|
|
866
1087
|
toolIndex.set(toolName, {
|
|
@@ -1190,6 +1411,35 @@ function buildHelpPayload(topic = "overview", options = {}) {
|
|
|
1190
1411
|
: "Use APIOSK_PRIVATE_KEY if you need autonomous payment on the public server mode",
|
|
1191
1412
|
],
|
|
1192
1413
|
},
|
|
1414
|
+
discovery: {
|
|
1415
|
+
topic: "discovery",
|
|
1416
|
+
summary:
|
|
1417
|
+
"apiosk_discover searches the whole x402 ecosystem in ONE call and returns ranked endpoints. This is the full list of sources it can explore. Call it whenever the user wants real/live/paid data.",
|
|
1418
|
+
searched_by_default: [
|
|
1419
|
+
"apiosk — the Apiosk catalog: first-party listings PLUS federated externals (imported from providers' /.well-known/x402, the APILayer & ApyHub ecosystems, direct provider integrations, and selected MCP skills). Paid via apiosk_execute.",
|
|
1420
|
+
"bazaar — the Coinbase x402 Bazaar (~25k resources), searched LIVE every call. The central shared index most marketplaces + MCPs publish into. Paid via apiosk_fetch_paid.",
|
|
1421
|
+
],
|
|
1422
|
+
opt_in_pass_sources_or_all: [
|
|
1423
|
+
"x402-list — x402-list.com public directory (free REST).",
|
|
1424
|
+
"x402-direct — x402.direct search engine with trust scores (free REST).",
|
|
1425
|
+
"agentic-market — Coinbase Agentic.Market directory (free REST).",
|
|
1426
|
+
"thirdweb — public thirdweb Payments x402 resource index (free REST).",
|
|
1427
|
+
"payai — public PayAI facilitator discovery mirror (free REST).",
|
|
1428
|
+
"x402engine — direct manifest with paid AI/media/code/web endpoints.",
|
|
1429
|
+
"anchor-x402 — direct manifest with paid primitives and LLM endpoints.",
|
|
1430
|
+
"x402scan — paid full-text resource search; returned as an inspect/fetch-paid pointer, never auto-paid.",
|
|
1431
|
+
"apify — paid x402 prepaid-token endpoint plus the public Actor catalog; never auto-paid.",
|
|
1432
|
+
"wellknown — probe a specific host's /.well-known/x402 (needs probe_hosts).",
|
|
1433
|
+
"Use sources:['all'] to fan out to all directly wired free REST sources at once. Paid sources remain explicit opt-ins.",
|
|
1434
|
+
],
|
|
1435
|
+
indexed_or_reference_only: [
|
|
1436
|
+
"x402list.fun (paid MCP), awesome-x402 (markdown), and facilitators Nevermined / @swader / x402.rs — reference, not directly searchable.",
|
|
1437
|
+
],
|
|
1438
|
+
important_note:
|
|
1439
|
+
"These are DISCOVERY sources (where the agent LOOKS for endpoints) — NOT the same as a catalog listing's `source` field (where one existing entry was imported from). The Bazaar is queried by default, so external endpoints surface without passing sources; pass sources:['all'] to also sweep the other public directories.",
|
|
1440
|
+
how_to:
|
|
1441
|
+
"apiosk_discover({ query, segments, sources }) → one ranked list; each result tagged with `source`, `trust_tier`, and `executable_via`.",
|
|
1442
|
+
},
|
|
1193
1443
|
payments: {
|
|
1194
1444
|
topic: "payments",
|
|
1195
1445
|
summary: "Paid Apiosk APIs can return x402 payment requirements if the client is not configured to settle automatically.",
|
|
@@ -1226,10 +1476,10 @@ function buildHelpPayload(topic = "overview", options = {}) {
|
|
|
1226
1476
|
"The connect string identifies the buyer's managed wallet and connect token; APIO_WALLET_* limits bound the USDC rail. See help topic 'setup' for the connect string format.",
|
|
1227
1477
|
provider_settlement: {
|
|
1228
1478
|
what_it_is:
|
|
1229
|
-
"The settlement_rails above are how BUYERS pay. This is the PROVIDER (seller) side: how an API owner receives their earnings. Agents do not interact with it
|
|
1479
|
+
"The settlement_rails above are how BUYERS pay. This is the PROVIDER (seller) side: how an API owner receives their earnings. Agents do not interact with it, it is provider-account configuration in the provider portal.",
|
|
1230
1480
|
how_it_works: [
|
|
1231
1481
|
"USDC earnings settle to the API's payout wallet (apis.wallet_address), which the portal links to a verified, Monerium-linked payout_wallets row.",
|
|
1232
|
-
"Crypto -> EUR off-ramp mandate (optional): the provider signs an EIP-191 authorization and an on-chain mandate (ApioskOfframpExecutor on Base). Apiosk's offramp keeper then auto-converts their accumulated USDC to EURe via Monerium and redeems it to their IBAN over SEPA once the balance crosses their bundle threshold. Non-custodial
|
|
1482
|
+
"Crypto -> EUR off-ramp mandate (optional): the provider signs an EIP-191 authorization and an on-chain mandate (ApioskOfframpExecutor on Base). Apiosk's offramp keeper then auto-converts their accumulated USDC to EURe via Monerium and redeems it to their IBAN over SEPA once the balance crosses their bundle threshold. Non-custodial, the keeper can never redirect funds or exceed the provider's on-chain per-run cap or cooldown.",
|
|
1233
1483
|
],
|
|
1234
1484
|
},
|
|
1235
1485
|
},
|
|
@@ -1266,7 +1516,7 @@ function buildHelpPayload(topic = "overview", options = {}) {
|
|
|
1266
1516
|
"slug must use lowercase letters, numbers, and hyphens",
|
|
1267
1517
|
],
|
|
1268
1518
|
identity_note:
|
|
1269
|
-
"These tools manage listings via WALLET SIGNATURE
|
|
1519
|
+
"These tools manage listings via WALLET SIGNATURE, the community/MCP publish channel, owned by a synthetic platform account. That is a different identity from the provider portal, where providers manage APIs under a Supabase-auth account (owner_id) after Monerium KYB, and where listings auto-verify on create. A listing published here and one created in the portal are managed through different identities and do not currently round-trip between the two surfaces.",
|
|
1270
1520
|
},
|
|
1271
1521
|
configure: {
|
|
1272
1522
|
topic: "configure",
|
|
@@ -1355,8 +1605,10 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1355
1605
|
const providedDashboardManager = options.walletManager || null;
|
|
1356
1606
|
const hostedAuthEnabled = options.hostedAuthEnabled === true;
|
|
1357
1607
|
const localWalletStore =
|
|
1358
|
-
|
|
1359
|
-
|
|
1608
|
+
hostedAuthEnabled
|
|
1609
|
+
? null
|
|
1610
|
+
: options.localWalletStore ||
|
|
1611
|
+
(options.enableLocalWallets === false ? null : createLocalWalletStore(env));
|
|
1360
1612
|
const cache = {
|
|
1361
1613
|
catalog: null,
|
|
1362
1614
|
expiresAt: 0,
|
|
@@ -1366,6 +1618,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1366
1618
|
};
|
|
1367
1619
|
|
|
1368
1620
|
async function getActiveExecutionWallet() {
|
|
1621
|
+
if (hostedAuthEnabled) return null;
|
|
1369
1622
|
try {
|
|
1370
1623
|
const envWallet = resolveEnvPrivateWallet(env);
|
|
1371
1624
|
if (envWallet) return envWallet;
|
|
@@ -1378,6 +1631,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1378
1631
|
}
|
|
1379
1632
|
|
|
1380
1633
|
async function getSavedConfig() {
|
|
1634
|
+
if (hostedAuthEnabled) return null;
|
|
1381
1635
|
return readLocalApioskConfig(env);
|
|
1382
1636
|
}
|
|
1383
1637
|
|
|
@@ -1539,7 +1793,16 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1539
1793
|
|
|
1540
1794
|
function getStaticTools(authInfo = null) {
|
|
1541
1795
|
if (hostedAuthEnabled) {
|
|
1542
|
-
|
|
1796
|
+
// Escape hatch: expose the full 28-tool surface when explicitly opted in.
|
|
1797
|
+
if (env.APIOSK_MCP_FULL_TOOLS === "true") {
|
|
1798
|
+
return [...HOSTED_REMOTE_TOOLS];
|
|
1799
|
+
}
|
|
1800
|
+
// Providers authenticate with a sk_live_ key → focused publishing surface.
|
|
1801
|
+
if (trimString(authInfo?.extra?.apiosk_provider_key)) {
|
|
1802
|
+
return [...HOSTED_PROVIDER_TOOLS];
|
|
1803
|
+
}
|
|
1804
|
+
// Buyers (OAuth / connect token / pre-auth) → the lean agentic surface.
|
|
1805
|
+
return [...HOSTED_BUYER_TOOLS];
|
|
1543
1806
|
}
|
|
1544
1807
|
|
|
1545
1808
|
const tools = [...DISCOVERY_TOOLS];
|
|
@@ -1550,6 +1813,14 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1550
1813
|
tools.push(...DASHBOARD_WALLET_TOOLS);
|
|
1551
1814
|
}
|
|
1552
1815
|
|
|
1816
|
+
// Provider-token x402 publishing works in every mode: hosted callers pass
|
|
1817
|
+
// Authorization: Bearer sk_live_…, stdio callers set APIOSK_PROVIDER_TOKEN.
|
|
1818
|
+
tools.push(...PUBLISHER_TOOLS);
|
|
1819
|
+
|
|
1820
|
+
// External x402 pay works with any connect token (env APIOSK_CONNECT_TOKEN in
|
|
1821
|
+
// stdio, or a request-scoped token on the hosted surface).
|
|
1822
|
+
tools.push(...EXTERNAL_PAY_TOOLS);
|
|
1823
|
+
|
|
1553
1824
|
return tools;
|
|
1554
1825
|
}
|
|
1555
1826
|
|
|
@@ -1568,6 +1839,51 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1568
1839
|
return cache.catalog;
|
|
1569
1840
|
}
|
|
1570
1841
|
|
|
1842
|
+
async function resolveExecutableApiSlug(slug, authInfo = null) {
|
|
1843
|
+
const requested = normalizeCatalogToken(slug);
|
|
1844
|
+
if (!requested) return slug;
|
|
1845
|
+
|
|
1846
|
+
const catalog = await getCatalog(false, authInfo);
|
|
1847
|
+
const exact = catalog.find((api) => normalizeCatalogToken(api.slug) === requested);
|
|
1848
|
+
if (exact) return exact.slug;
|
|
1849
|
+
|
|
1850
|
+
const aliases = new Map([
|
|
1851
|
+
["weather", "open-meteo"],
|
|
1852
|
+
["weather-api", "open-meteo"],
|
|
1853
|
+
["hugen-weather", "open-meteo"],
|
|
1854
|
+
["open-meteo-weather", "open-meteo"],
|
|
1855
|
+
]);
|
|
1856
|
+
const alias = aliases.get(requested);
|
|
1857
|
+
if (alias && catalog.some((api) => normalizeCatalogToken(api.slug) === alias)) {
|
|
1858
|
+
return alias;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
if (isWeatherLikeSlug(requested)) {
|
|
1862
|
+
const weather = catalog.find((api) => {
|
|
1863
|
+
const haystack = [
|
|
1864
|
+
api.slug,
|
|
1865
|
+
api.name,
|
|
1866
|
+
api.description,
|
|
1867
|
+
api.category,
|
|
1868
|
+
api.raw_category,
|
|
1869
|
+
...(Array.isArray(api.listing_metadata?.tags) ? api.listing_metadata.tags : []),
|
|
1870
|
+
]
|
|
1871
|
+
.map(normalizeCatalogToken)
|
|
1872
|
+
.join(" ");
|
|
1873
|
+
|
|
1874
|
+
return (
|
|
1875
|
+
api.active !== false &&
|
|
1876
|
+
api.verified !== false &&
|
|
1877
|
+
/weather|meteo|forecast/.test(haystack)
|
|
1878
|
+
);
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
if (weather?.slug) return weather.slug;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
return slug;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1571
1887
|
async function getTools(force = false, authInfo = null) {
|
|
1572
1888
|
if (!force && cache.dynamicTools && Date.now() < cache.expiresAt) {
|
|
1573
1889
|
return [...getStaticTools(authInfo), ...cache.dynamicTools];
|
|
@@ -1981,12 +2297,20 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1981
2297
|
});
|
|
1982
2298
|
|
|
1983
2299
|
const catalog = response.apis || [];
|
|
2300
|
+
const sourceMatches = searchKnownSources(argumentsObject.search, {
|
|
2301
|
+
limit: argumentsObject.limit || DEFAULT_LIMIT,
|
|
2302
|
+
});
|
|
1984
2303
|
await getTools(false, authInfo);
|
|
1985
2304
|
const capability = await resolvePaymentCapability(authInfo);
|
|
1986
2305
|
|
|
1987
2306
|
return content({
|
|
1988
2307
|
apis: catalog.map((api) => buildCatalogEntry(api, cache.toolNamesBySlug.get(api.slug) || null)),
|
|
2308
|
+
sources: sourceMatches,
|
|
1989
2309
|
meta: response.meta,
|
|
2310
|
+
source_meta: {
|
|
2311
|
+
total: sourceMatches.length,
|
|
2312
|
+
note: "These are direct x402 discovery sources, not Apiosk catalog API listings. Their public and paid endpoints are included inline.",
|
|
2313
|
+
},
|
|
1990
2314
|
payment: buildDiscoveryPaymentHint({
|
|
1991
2315
|
capability,
|
|
1992
2316
|
mode: capability.mode,
|
|
@@ -1995,7 +2319,9 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1995
2319
|
for_providers:
|
|
1996
2320
|
"Listing your own API? Call apiosk_payment_guide with role='provider' (or apiosk_publish_api) to publish it for other agents.",
|
|
1997
2321
|
next_steps:
|
|
1998
|
-
|
|
2322
|
+
sourceMatches.length > 0
|
|
2323
|
+
? "For a matched source, pass its discover_source to apiosk_discover. For endpoints marked payment_required=true, call apiosk_inspect_x402 first and only then apiosk_fetch_paid after the user confirms the live price. Catalog APIs still use apiosk_get_api/apiosk_execute."
|
|
2324
|
+
: "Call apiosk_get_api for full metadata plus a per-listing payment block, use the API-specific tool directly when tool_name is present, or call apiosk_payment_guide to learn exactly how to settle a paid call.",
|
|
1999
2325
|
});
|
|
2000
2326
|
}
|
|
2001
2327
|
|
|
@@ -2067,13 +2393,53 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2067
2393
|
});
|
|
2068
2394
|
}
|
|
2069
2395
|
|
|
2396
|
+
// Agentic discovery: aggregate + rank candidate x402 endpoints across sources
|
|
2397
|
+
// (Phase 1: the Apiosk catalog, which already includes federated externals).
|
|
2398
|
+
// Reuses the request-scoped client so catalog reads honour the same gateway
|
|
2399
|
+
// base URL and connect-token threading as every other tool.
|
|
2400
|
+
async function handleDiscover(argumentsObject = {}, authInfo = null) {
|
|
2401
|
+
const client = await getClient(authInfo);
|
|
2402
|
+
const savedConfig = await getSavedConfig().catch(() => null);
|
|
2403
|
+
return runDiscover(argumentsObject, {
|
|
2404
|
+
listApis: (params) => client.listApis(params),
|
|
2405
|
+
gatewayBaseUrl: resolveGatewayBaseUrl(env, savedConfig),
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// Read an arbitrary URL's x402 402 terms without paying. Read-only probe; no
|
|
2410
|
+
// wallet/connect token is used, so it needs no auth.
|
|
2411
|
+
async function handleInspect(argumentsObject = {}, authInfo = null) {
|
|
2412
|
+
const savedConfig = await getSavedConfig().catch(() => null);
|
|
2413
|
+
let gatewayHost = "";
|
|
2414
|
+
try {
|
|
2415
|
+
gatewayHost = new URL(resolveGatewayBaseUrl(env, savedConfig)).hostname;
|
|
2416
|
+
} catch {
|
|
2417
|
+
gatewayHost = "";
|
|
2418
|
+
}
|
|
2419
|
+
return runInspect(argumentsObject, { gatewayHost });
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
// Pay an external (non-Apiosk-hosted) x402 endpoint through the gateway payer
|
|
2423
|
+
// proxy, using the same connect token the runtime threads to the gateway for
|
|
2424
|
+
// catalog settlement. The gateway enforces spend caps and does the signing.
|
|
2425
|
+
async function handleFetchPaid(argumentsObject = {}, authInfo = null) {
|
|
2426
|
+
const savedConfig = await getSavedConfig().catch(() => null);
|
|
2427
|
+
const requestConnectToken = trimString(authInfo?.extra?.apiosk_connect_token);
|
|
2428
|
+
const envConnectToken = trimString(env.APIOSK_CONNECT_TOKEN || savedConfig?.connect_token);
|
|
2429
|
+
return runFetchPaid(argumentsObject, {
|
|
2430
|
+
connectToken: requestConnectToken || envConnectToken,
|
|
2431
|
+
gatewayBaseUrl: resolveGatewayBaseUrl(env, savedConfig),
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2070
2435
|
async function handleExecute(argumentsObject = {}, authInfo = null) {
|
|
2071
2436
|
if (!argumentsObject.slug) {
|
|
2072
2437
|
return errorContent("Missing required field: slug");
|
|
2073
2438
|
}
|
|
2074
2439
|
|
|
2075
2440
|
const client = await getClient(authInfo);
|
|
2076
|
-
const
|
|
2441
|
+
const resolvedSlug = await resolveExecutableApiSlug(argumentsObject.slug, authInfo);
|
|
2442
|
+
const result = await client.execute(resolvedSlug, argumentsObject.input, {
|
|
2077
2443
|
operation: argumentsObject.operation,
|
|
2078
2444
|
query: argumentsObject.query,
|
|
2079
2445
|
pathParams: argumentsObject.path_params,
|
|
@@ -2082,7 +2448,15 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2082
2448
|
},
|
|
2083
2449
|
});
|
|
2084
2450
|
|
|
2085
|
-
return content(
|
|
2451
|
+
return content(
|
|
2452
|
+
resolvedSlug === argumentsObject.slug
|
|
2453
|
+
? result
|
|
2454
|
+
: {
|
|
2455
|
+
requested_slug: argumentsObject.slug,
|
|
2456
|
+
resolved_slug: resolvedSlug,
|
|
2457
|
+
result,
|
|
2458
|
+
}
|
|
2459
|
+
);
|
|
2086
2460
|
}
|
|
2087
2461
|
|
|
2088
2462
|
async function handleDynamicExecute(tool, argumentsObject = {}, authInfo = null) {
|
|
@@ -2274,11 +2648,37 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2274
2648
|
});
|
|
2275
2649
|
}
|
|
2276
2650
|
|
|
2651
|
+
// Hosted (OAuth / wallet sign-in) sessions manage wallets straight against
|
|
2652
|
+
// Supabase REST with the caller's own JWT — the legacy dashboard backend
|
|
2653
|
+
// these tools used to proxy to no longer exists (dashboard.apiosk.com is now
|
|
2654
|
+
// the provider-portal SPA, whose catch-all answers /api/* with index.html).
|
|
2655
|
+
// The requestDashboard proxy path is kept only for stdio setups that point
|
|
2656
|
+
// APIOSK_CONTROL_PLANE_URL at their own control plane.
|
|
2657
|
+
function hostedWalletContext(authInfo = null) {
|
|
2658
|
+
const sessionToken = resolveRequestDashboardUserToken(authInfo);
|
|
2659
|
+
if (!sessionToken) return null;
|
|
2660
|
+
return {
|
|
2661
|
+
env,
|
|
2662
|
+
sessionToken,
|
|
2663
|
+
userId: trimString(authInfo?.extra?.userId),
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2277
2667
|
async function handleWalletList(authInfo = null) {
|
|
2668
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2669
|
+
if (hosted) {
|
|
2670
|
+
return content(await hostedListWallets(hosted));
|
|
2671
|
+
}
|
|
2278
2672
|
return content(await requestDashboard("/api/agent-wallets", {}, {}, authInfo));
|
|
2279
2673
|
}
|
|
2280
2674
|
|
|
2281
2675
|
async function handleWalletCreate(argumentsObject = {}, authInfo = null) {
|
|
2676
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2677
|
+
if (hosted) {
|
|
2678
|
+
// Key derivation + encryption lived in the retired dashboard backend, so
|
|
2679
|
+
// hosted sessions get an honest explanation instead of proxied HTML.
|
|
2680
|
+
return errorContent(hostedCreateWalletUnavailable());
|
|
2681
|
+
}
|
|
2282
2682
|
return content(
|
|
2283
2683
|
await requestDashboard(
|
|
2284
2684
|
"/api/agent-wallets",
|
|
@@ -2297,6 +2697,22 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2297
2697
|
return errorContent("Missing required field: wallet_id");
|
|
2298
2698
|
}
|
|
2299
2699
|
|
|
2700
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2701
|
+
if (hosted) {
|
|
2702
|
+
return content(
|
|
2703
|
+
await hostedUpdateWallet({
|
|
2704
|
+
...hosted,
|
|
2705
|
+
walletId: argumentsObject.wallet_id,
|
|
2706
|
+
label: argumentsObject.label,
|
|
2707
|
+
status: argumentsObject.status,
|
|
2708
|
+
dailyLimitUsdc: argumentsObject.daily_limit_usdc,
|
|
2709
|
+
perTxLimitUsdc: argumentsObject.per_tx_limit_usdc,
|
|
2710
|
+
color: argumentsObject.color,
|
|
2711
|
+
icon: argumentsObject.icon,
|
|
2712
|
+
})
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2300
2716
|
const { wallet_id, ...updates } = argumentsObject;
|
|
2301
2717
|
return content(
|
|
2302
2718
|
await requestDashboard(
|
|
@@ -2316,6 +2732,13 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2316
2732
|
return errorContent("Missing required field: wallet_id");
|
|
2317
2733
|
}
|
|
2318
2734
|
|
|
2735
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2736
|
+
if (hosted) {
|
|
2737
|
+
return content(
|
|
2738
|
+
await hostedDeleteWallet({ ...hosted, walletId: argumentsObject.wallet_id })
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2319
2742
|
return content(
|
|
2320
2743
|
await requestDashboard(
|
|
2321
2744
|
`/api/agent-wallets/${argumentsObject.wallet_id}`,
|
|
@@ -2333,6 +2756,17 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2333
2756
|
return errorContent("Missing required field: wallet_id");
|
|
2334
2757
|
}
|
|
2335
2758
|
|
|
2759
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2760
|
+
if (hosted) {
|
|
2761
|
+
return content(
|
|
2762
|
+
await hostedWalletActivity({
|
|
2763
|
+
...hosted,
|
|
2764
|
+
walletId: argumentsObject.wallet_id,
|
|
2765
|
+
limit: argumentsObject.limit,
|
|
2766
|
+
})
|
|
2767
|
+
);
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2336
2770
|
const params = new URLSearchParams();
|
|
2337
2771
|
if (argumentsObject.page !== undefined) params.set("page", String(argumentsObject.page));
|
|
2338
2772
|
if (argumentsObject.limit !== undefined) params.set("limit", String(argumentsObject.limit));
|
|
@@ -2353,6 +2787,20 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2353
2787
|
return errorContent("Missing required field: wallet_id");
|
|
2354
2788
|
}
|
|
2355
2789
|
|
|
2790
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2791
|
+
if (hosted) {
|
|
2792
|
+
const created = await hostedCreateWalletToken({
|
|
2793
|
+
...hosted,
|
|
2794
|
+
walletId: argumentsObject.wallet_id,
|
|
2795
|
+
name: argumentsObject.token_name,
|
|
2796
|
+
revokeExisting: argumentsObject.revoke_existing === true,
|
|
2797
|
+
});
|
|
2798
|
+
return content({
|
|
2799
|
+
...created,
|
|
2800
|
+
connect_string: `apiosk:connect:${created.connect_token}`,
|
|
2801
|
+
});
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2356
2804
|
const { wallet_id, ...body } = argumentsObject;
|
|
2357
2805
|
return content(
|
|
2358
2806
|
await requestDashboard(
|
|
@@ -2372,6 +2820,13 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2372
2820
|
return errorContent("Missing required field: wallet_id");
|
|
2373
2821
|
}
|
|
2374
2822
|
|
|
2823
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2824
|
+
if (hosted) {
|
|
2825
|
+
return content(
|
|
2826
|
+
await hostedListWalletTokens({ ...hosted, walletId: argumentsObject.wallet_id })
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2375
2830
|
return content(
|
|
2376
2831
|
await requestDashboard(
|
|
2377
2832
|
`/api/agent-wallets/${argumentsObject.wallet_id}/api-keys`,
|
|
@@ -2387,6 +2842,19 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2387
2842
|
return errorContent("Missing required field: wallet_id");
|
|
2388
2843
|
}
|
|
2389
2844
|
|
|
2845
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2846
|
+
if (hosted) {
|
|
2847
|
+
return content(
|
|
2848
|
+
await hostedCreateWalletToken({
|
|
2849
|
+
...hosted,
|
|
2850
|
+
walletId: argumentsObject.wallet_id,
|
|
2851
|
+
name: argumentsObject.name,
|
|
2852
|
+
expirationDays: argumentsObject.expiration_days,
|
|
2853
|
+
revokeExisting: argumentsObject.revoke_existing === true,
|
|
2854
|
+
})
|
|
2855
|
+
);
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2390
2858
|
const { wallet_id, ...body } = argumentsObject;
|
|
2391
2859
|
return content(
|
|
2392
2860
|
await requestDashboard(
|
|
@@ -2409,6 +2877,20 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2409
2877
|
return errorContent("Missing required field: key_id");
|
|
2410
2878
|
}
|
|
2411
2879
|
|
|
2880
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2881
|
+
if (hosted) {
|
|
2882
|
+
return content(
|
|
2883
|
+
await hostedUpdateWalletToken({
|
|
2884
|
+
...hosted,
|
|
2885
|
+
walletId: argumentsObject.wallet_id,
|
|
2886
|
+
keyId: argumentsObject.key_id,
|
|
2887
|
+
name: argumentsObject.name,
|
|
2888
|
+
expirationDays: argumentsObject.expiration_days,
|
|
2889
|
+
revoke: argumentsObject.revoke,
|
|
2890
|
+
})
|
|
2891
|
+
);
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2412
2894
|
const { wallet_id, key_id, ...body } = argumentsObject;
|
|
2413
2895
|
return content(
|
|
2414
2896
|
await requestDashboard(
|
|
@@ -2431,6 +2913,17 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2431
2913
|
return errorContent("Missing required field: key_id");
|
|
2432
2914
|
}
|
|
2433
2915
|
|
|
2916
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2917
|
+
if (hosted) {
|
|
2918
|
+
return content(
|
|
2919
|
+
await hostedDeleteWalletToken({
|
|
2920
|
+
...hosted,
|
|
2921
|
+
walletId: argumentsObject.wallet_id,
|
|
2922
|
+
keyId: argumentsObject.key_id,
|
|
2923
|
+
})
|
|
2924
|
+
);
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2434
2927
|
return content(
|
|
2435
2928
|
await requestDashboard(
|
|
2436
2929
|
`/api/agent-wallets/${argumentsObject.wallet_id}/api-keys/${argumentsObject.key_id}`,
|
|
@@ -2470,7 +2963,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2470
2963
|
}
|
|
2471
2964
|
|
|
2472
2965
|
// Force include_qr_data_url so we can append an inline image content
|
|
2473
|
-
// block below
|
|
2966
|
+
// block below, clients that render images (Claude Desktop, MCP
|
|
2474
2967
|
// Inspector) will then show the funding QR right next to the
|
|
2475
2968
|
// newly-created wallet without a follow-up tool call.
|
|
2476
2969
|
created.configure = await buildConfigurePayload({
|
|
@@ -2523,7 +3016,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2523
3016
|
`Token: USDC (${baseUsdcContract})`,
|
|
2524
3017
|
"",
|
|
2525
3018
|
"WARNING: only Base mainnet USDC. Sending from Ethereum, Polygon, Solana,",
|
|
2526
|
-
"or any other network will permanently lose the funds
|
|
3019
|
+
"or any other network will permanently lose the funds, this address is",
|
|
2527
3020
|
"Base only.",
|
|
2528
3021
|
"",
|
|
2529
3022
|
`Block explorer: ${receive.explorer_url}`,
|
|
@@ -2538,7 +3031,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2538
3031
|
|
|
2539
3032
|
// Render the QR inline via MCP image content when the buyer's client
|
|
2540
3033
|
// supports images (Claude Desktop, the MCP Inspector, etc.). Falls back
|
|
2541
|
-
// gracefully on terminals without image support
|
|
3034
|
+
// gracefully on terminals without image support, the text block above
|
|
2542
3035
|
// still has the address and the ANSI QR.
|
|
2543
3036
|
const dataUrl = receive.qr_code_data_url;
|
|
2544
3037
|
if (typeof dataUrl === "string" && dataUrl.startsWith("data:image/png;base64,")) {
|
|
@@ -3018,12 +3511,92 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
3018
3511
|
});
|
|
3019
3512
|
}
|
|
3020
3513
|
|
|
3514
|
+
// A 402 means the buyer's configured rails could not cover the call. Tell
|
|
3515
|
+
// the caller what THEIR next step is, based on how this request
|
|
3516
|
+
// authenticated, instead of always giving local-stdio advice.
|
|
3517
|
+
function buildPaymentRequiredHint(authInfo = null) {
|
|
3518
|
+
const extra = authInfo?.extra || {};
|
|
3519
|
+
const connectWallet = trimString(
|
|
3520
|
+
extra.apiosk_connect_wallet_address || extra.apioskConnectWalletAddress
|
|
3521
|
+
);
|
|
3522
|
+
|
|
3523
|
+
if (trimString(extra.apiosk_connect_token)) {
|
|
3524
|
+
const walletNote = connectWallet ? ` (${connectWallet})` : "";
|
|
3525
|
+
return (
|
|
3526
|
+
`Your managed Apiosk wallet${walletNote} could not cover this call — it is out of USDC, ` +
|
|
3527
|
+
"over its spending limit, or missing settlement approval. Send USDC on Base mainnet " +
|
|
3528
|
+
"(chain 8453) to the wallet address and retry; payment then settles automatically. " +
|
|
3529
|
+
"Use apiosk_list_wallets to see your wallets and funding instructions."
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
if (hasRequestScopedDashboardAccess(authInfo)) {
|
|
3534
|
+
return (
|
|
3535
|
+
"You are signed in, but no payable managed wallet is linked to this session. " +
|
|
3536
|
+
"Use apiosk_list_wallets to check your wallets: if one exists, fund it with USDC on Base mainnet " +
|
|
3537
|
+
"and re-authorize the Apiosk app so a fresh payment token is minted; if none exists, set one up " +
|
|
3538
|
+
"in the Apiosk buyer portal first."
|
|
3539
|
+
);
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
return hostedAuthEnabled
|
|
3543
|
+
? "Authorize Apiosk in your MCP client, then use apiosk_list_wallets to confirm that a funded managed wallet is linked before retrying."
|
|
3544
|
+
: "Run apiosk_get_started in the local stdio package, or configure APIOSK_PRIVATE_KEY, to enable automatic x402 settlement.";
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
// Observability wrapper: time + log every tools/call dispatch to mcp_tool_calls
|
|
3548
|
+
// (fire-and-forget — a logging failure never affects the tool result). See
|
|
3549
|
+
// observability.mjs. Raw tokens/args are never persisted (hash + key-names only).
|
|
3021
3550
|
async function callTool(name, argumentsObject = {}, authInfo = null) {
|
|
3551
|
+
const startedAt = Date.now();
|
|
3552
|
+
let outcome = "ok";
|
|
3553
|
+
let errorCode = null;
|
|
3554
|
+
try {
|
|
3555
|
+
const result = await dispatchTool(name, argumentsObject, authInfo);
|
|
3556
|
+
if (result && result.isError) outcome = "error";
|
|
3557
|
+
else if (result && result.status === "payment_required") outcome = "refused";
|
|
3558
|
+
return result;
|
|
3559
|
+
} catch (error) {
|
|
3560
|
+
outcome = "error";
|
|
3561
|
+
errorCode = (error && (error.code || error.name)) || null;
|
|
3562
|
+
throw error;
|
|
3563
|
+
} finally {
|
|
3564
|
+
try {
|
|
3565
|
+
logToolCall(env, {
|
|
3566
|
+
toolName: name,
|
|
3567
|
+
outcome,
|
|
3568
|
+
errorCode,
|
|
3569
|
+
latencyMs: Date.now() - startedAt,
|
|
3570
|
+
authInfo,
|
|
3571
|
+
argKeys: argumentsObject && typeof argumentsObject === "object" ? Object.keys(argumentsObject) : [],
|
|
3572
|
+
});
|
|
3573
|
+
} catch {
|
|
3574
|
+
/* observability must never break a tool call */
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
async function dispatchTool(name, argumentsObject = {}, authInfo = null) {
|
|
3022
3580
|
try {
|
|
3581
|
+
if (hostedAuthEnabled && LOCAL_ONLY_TOOL_NAMES.has(name)) {
|
|
3582
|
+
return content({
|
|
3583
|
+
status: "unsupported",
|
|
3584
|
+
error_code: "tool.local_only",
|
|
3585
|
+
message: `${name} is only available in the local stdio package and cannot run on the hosted Apiosk MCP server.`,
|
|
3586
|
+
next_steps: [
|
|
3587
|
+
"Use apiosk_search, apiosk_explore, or apiosk_discover anonymously.",
|
|
3588
|
+
"For paid or account-specific actions, authorize Apiosk in the MCP client and use apiosk_list_wallets or apiosk_execute.",
|
|
3589
|
+
],
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3023
3593
|
if (name === "apiosk_help") return await handleHelp(argumentsObject);
|
|
3024
3594
|
if (name === "apiosk_payment_guide") return await handlePaymentGuide(argumentsObject, authInfo);
|
|
3025
3595
|
if (name === "apiosk_explore") return await handleExplore(argumentsObject, authInfo);
|
|
3026
3596
|
if (name === "apiosk_search") return await handleSearch(argumentsObject, authInfo);
|
|
3597
|
+
if (name === "apiosk_discover") return await handleDiscover(argumentsObject, authInfo);
|
|
3598
|
+
if (name === "apiosk_inspect_x402") return await handleInspect(argumentsObject, authInfo);
|
|
3599
|
+
if (name === "apiosk_fetch_paid") return await handleFetchPaid(argumentsObject, authInfo);
|
|
3027
3600
|
if (name === "apiosk_get_api" || name === "apiosk_metadata") {
|
|
3028
3601
|
return await handleGetApi(argumentsObject, authInfo);
|
|
3029
3602
|
}
|
|
@@ -3042,6 +3615,8 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
3042
3615
|
if (name === "apiosk_wallet_reveal_secret") return await handleLocalWalletReveal(argumentsObject);
|
|
3043
3616
|
if (name === "apiosk_wallet_save_secret") return await handleLocalWalletSave(argumentsObject);
|
|
3044
3617
|
|
|
3618
|
+
if (isPublisherTool(name)) return await handlePublisherTool(name, argumentsObject, authInfo, { env });
|
|
3619
|
+
|
|
3045
3620
|
if (name === "apiosk_publish_api") return await handlePublishApi(argumentsObject);
|
|
3046
3621
|
if (name === "apiosk_list_my_apis") return await handleListMyApis(argumentsObject);
|
|
3047
3622
|
if (name === "apiosk_update_api") return await handleUpdateApi(argumentsObject);
|
|
@@ -3068,10 +3643,14 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
3068
3643
|
return await handleDynamicExecute(tool, argumentsObject, authInfo);
|
|
3069
3644
|
} catch (error) {
|
|
3070
3645
|
if (error instanceof ApioskPaymentRequiredError) {
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3646
|
+
// A valid 402 is a business state, not an MCP protocol failure. Returning
|
|
3647
|
+
// isError made ChatGPT/Claude collapse it into JSON-RPC -32603 and hid
|
|
3648
|
+
// the actionable funding guidance from the model.
|
|
3649
|
+
return content({
|
|
3650
|
+
status: "payment_required",
|
|
3651
|
+
error_code: "payment.wallet_unfunded_or_unavailable",
|
|
3652
|
+
message: error.message,
|
|
3653
|
+
hint: buildPaymentRequiredHint(authInfo),
|
|
3075
3654
|
payment_required: error.paymentRequired,
|
|
3076
3655
|
});
|
|
3077
3656
|
}
|
|
@@ -3083,7 +3662,19 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
3083
3662
|
}
|
|
3084
3663
|
|
|
3085
3664
|
return {
|
|
3086
|
-
listTools: (authInfo = null) =>
|
|
3665
|
+
listTools: async (authInfo = null) => {
|
|
3666
|
+
const tools = await getTools(false, authInfo);
|
|
3667
|
+
return Promise.all(
|
|
3668
|
+
tools.map(async (tool) =>
|
|
3669
|
+
normalizeToolForClient(tool, {
|
|
3670
|
+
hosted: hostedAuthEnabled,
|
|
3671
|
+
protectedTool: hostedAuthEnabled
|
|
3672
|
+
? await isToolProtected(tool.name, authInfo)
|
|
3673
|
+
: false,
|
|
3674
|
+
})
|
|
3675
|
+
)
|
|
3676
|
+
);
|
|
3677
|
+
},
|
|
3087
3678
|
isToolProtected,
|
|
3088
3679
|
callTool,
|
|
3089
3680
|
};
|