@apiosk/mcp 1.3.1 → 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 +85 -9
- package/docs/sepa-rail.md +13 -13
- package/dxt.json +4 -4
- package/logo-optimized-light.png +0 -0
- package/package.json +6 -4
- package/server.json +58 -3
- 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 +609 -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,6 +1009,18 @@ 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();
|
|
@@ -872,7 +1072,16 @@ function buildDynamicTools(catalog, reservedTools) {
|
|
|
872
1072
|
type: "object",
|
|
873
1073
|
additionalProperties: true,
|
|
874
1074
|
},
|
|
875
|
-
|
|
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
|
+
},
|
|
876
1085
|
});
|
|
877
1086
|
|
|
878
1087
|
toolIndex.set(toolName, {
|
|
@@ -1202,6 +1411,35 @@ function buildHelpPayload(topic = "overview", options = {}) {
|
|
|
1202
1411
|
: "Use APIOSK_PRIVATE_KEY if you need autonomous payment on the public server mode",
|
|
1203
1412
|
],
|
|
1204
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
|
+
},
|
|
1205
1443
|
payments: {
|
|
1206
1444
|
topic: "payments",
|
|
1207
1445
|
summary: "Paid Apiosk APIs can return x402 payment requirements if the client is not configured to settle automatically.",
|
|
@@ -1238,10 +1476,10 @@ function buildHelpPayload(topic = "overview", options = {}) {
|
|
|
1238
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.",
|
|
1239
1477
|
provider_settlement: {
|
|
1240
1478
|
what_it_is:
|
|
1241
|
-
"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.",
|
|
1242
1480
|
how_it_works: [
|
|
1243
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.",
|
|
1244
|
-
"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.",
|
|
1245
1483
|
],
|
|
1246
1484
|
},
|
|
1247
1485
|
},
|
|
@@ -1278,7 +1516,7 @@ function buildHelpPayload(topic = "overview", options = {}) {
|
|
|
1278
1516
|
"slug must use lowercase letters, numbers, and hyphens",
|
|
1279
1517
|
],
|
|
1280
1518
|
identity_note:
|
|
1281
|
-
"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.",
|
|
1282
1520
|
},
|
|
1283
1521
|
configure: {
|
|
1284
1522
|
topic: "configure",
|
|
@@ -1367,8 +1605,10 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1367
1605
|
const providedDashboardManager = options.walletManager || null;
|
|
1368
1606
|
const hostedAuthEnabled = options.hostedAuthEnabled === true;
|
|
1369
1607
|
const localWalletStore =
|
|
1370
|
-
|
|
1371
|
-
|
|
1608
|
+
hostedAuthEnabled
|
|
1609
|
+
? null
|
|
1610
|
+
: options.localWalletStore ||
|
|
1611
|
+
(options.enableLocalWallets === false ? null : createLocalWalletStore(env));
|
|
1372
1612
|
const cache = {
|
|
1373
1613
|
catalog: null,
|
|
1374
1614
|
expiresAt: 0,
|
|
@@ -1378,6 +1618,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1378
1618
|
};
|
|
1379
1619
|
|
|
1380
1620
|
async function getActiveExecutionWallet() {
|
|
1621
|
+
if (hostedAuthEnabled) return null;
|
|
1381
1622
|
try {
|
|
1382
1623
|
const envWallet = resolveEnvPrivateWallet(env);
|
|
1383
1624
|
if (envWallet) return envWallet;
|
|
@@ -1390,6 +1631,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1390
1631
|
}
|
|
1391
1632
|
|
|
1392
1633
|
async function getSavedConfig() {
|
|
1634
|
+
if (hostedAuthEnabled) return null;
|
|
1393
1635
|
return readLocalApioskConfig(env);
|
|
1394
1636
|
}
|
|
1395
1637
|
|
|
@@ -1551,7 +1793,16 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1551
1793
|
|
|
1552
1794
|
function getStaticTools(authInfo = null) {
|
|
1553
1795
|
if (hostedAuthEnabled) {
|
|
1554
|
-
|
|
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];
|
|
1555
1806
|
}
|
|
1556
1807
|
|
|
1557
1808
|
const tools = [...DISCOVERY_TOOLS];
|
|
@@ -1562,6 +1813,14 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1562
1813
|
tools.push(...DASHBOARD_WALLET_TOOLS);
|
|
1563
1814
|
}
|
|
1564
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
|
+
|
|
1565
1824
|
return tools;
|
|
1566
1825
|
}
|
|
1567
1826
|
|
|
@@ -1580,6 +1839,51 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1580
1839
|
return cache.catalog;
|
|
1581
1840
|
}
|
|
1582
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
|
+
|
|
1583
1887
|
async function getTools(force = false, authInfo = null) {
|
|
1584
1888
|
if (!force && cache.dynamicTools && Date.now() < cache.expiresAt) {
|
|
1585
1889
|
return [...getStaticTools(authInfo), ...cache.dynamicTools];
|
|
@@ -1993,12 +2297,20 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
1993
2297
|
});
|
|
1994
2298
|
|
|
1995
2299
|
const catalog = response.apis || [];
|
|
2300
|
+
const sourceMatches = searchKnownSources(argumentsObject.search, {
|
|
2301
|
+
limit: argumentsObject.limit || DEFAULT_LIMIT,
|
|
2302
|
+
});
|
|
1996
2303
|
await getTools(false, authInfo);
|
|
1997
2304
|
const capability = await resolvePaymentCapability(authInfo);
|
|
1998
2305
|
|
|
1999
2306
|
return content({
|
|
2000
2307
|
apis: catalog.map((api) => buildCatalogEntry(api, cache.toolNamesBySlug.get(api.slug) || null)),
|
|
2308
|
+
sources: sourceMatches,
|
|
2001
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
|
+
},
|
|
2002
2314
|
payment: buildDiscoveryPaymentHint({
|
|
2003
2315
|
capability,
|
|
2004
2316
|
mode: capability.mode,
|
|
@@ -2007,7 +2319,9 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2007
2319
|
for_providers:
|
|
2008
2320
|
"Listing your own API? Call apiosk_payment_guide with role='provider' (or apiosk_publish_api) to publish it for other agents.",
|
|
2009
2321
|
next_steps:
|
|
2010
|
-
|
|
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.",
|
|
2011
2325
|
});
|
|
2012
2326
|
}
|
|
2013
2327
|
|
|
@@ -2079,13 +2393,53 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2079
2393
|
});
|
|
2080
2394
|
}
|
|
2081
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
|
+
|
|
2082
2435
|
async function handleExecute(argumentsObject = {}, authInfo = null) {
|
|
2083
2436
|
if (!argumentsObject.slug) {
|
|
2084
2437
|
return errorContent("Missing required field: slug");
|
|
2085
2438
|
}
|
|
2086
2439
|
|
|
2087
2440
|
const client = await getClient(authInfo);
|
|
2088
|
-
const
|
|
2441
|
+
const resolvedSlug = await resolveExecutableApiSlug(argumentsObject.slug, authInfo);
|
|
2442
|
+
const result = await client.execute(resolvedSlug, argumentsObject.input, {
|
|
2089
2443
|
operation: argumentsObject.operation,
|
|
2090
2444
|
query: argumentsObject.query,
|
|
2091
2445
|
pathParams: argumentsObject.path_params,
|
|
@@ -2094,7 +2448,15 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2094
2448
|
},
|
|
2095
2449
|
});
|
|
2096
2450
|
|
|
2097
|
-
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
|
+
);
|
|
2098
2460
|
}
|
|
2099
2461
|
|
|
2100
2462
|
async function handleDynamicExecute(tool, argumentsObject = {}, authInfo = null) {
|
|
@@ -2286,11 +2648,37 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2286
2648
|
});
|
|
2287
2649
|
}
|
|
2288
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
|
+
|
|
2289
2667
|
async function handleWalletList(authInfo = null) {
|
|
2668
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2669
|
+
if (hosted) {
|
|
2670
|
+
return content(await hostedListWallets(hosted));
|
|
2671
|
+
}
|
|
2290
2672
|
return content(await requestDashboard("/api/agent-wallets", {}, {}, authInfo));
|
|
2291
2673
|
}
|
|
2292
2674
|
|
|
2293
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
|
+
}
|
|
2294
2682
|
return content(
|
|
2295
2683
|
await requestDashboard(
|
|
2296
2684
|
"/api/agent-wallets",
|
|
@@ -2309,6 +2697,22 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2309
2697
|
return errorContent("Missing required field: wallet_id");
|
|
2310
2698
|
}
|
|
2311
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
|
+
|
|
2312
2716
|
const { wallet_id, ...updates } = argumentsObject;
|
|
2313
2717
|
return content(
|
|
2314
2718
|
await requestDashboard(
|
|
@@ -2328,6 +2732,13 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2328
2732
|
return errorContent("Missing required field: wallet_id");
|
|
2329
2733
|
}
|
|
2330
2734
|
|
|
2735
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2736
|
+
if (hosted) {
|
|
2737
|
+
return content(
|
|
2738
|
+
await hostedDeleteWallet({ ...hosted, walletId: argumentsObject.wallet_id })
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2331
2742
|
return content(
|
|
2332
2743
|
await requestDashboard(
|
|
2333
2744
|
`/api/agent-wallets/${argumentsObject.wallet_id}`,
|
|
@@ -2345,6 +2756,17 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2345
2756
|
return errorContent("Missing required field: wallet_id");
|
|
2346
2757
|
}
|
|
2347
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
|
+
|
|
2348
2770
|
const params = new URLSearchParams();
|
|
2349
2771
|
if (argumentsObject.page !== undefined) params.set("page", String(argumentsObject.page));
|
|
2350
2772
|
if (argumentsObject.limit !== undefined) params.set("limit", String(argumentsObject.limit));
|
|
@@ -2365,6 +2787,20 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2365
2787
|
return errorContent("Missing required field: wallet_id");
|
|
2366
2788
|
}
|
|
2367
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
|
+
|
|
2368
2804
|
const { wallet_id, ...body } = argumentsObject;
|
|
2369
2805
|
return content(
|
|
2370
2806
|
await requestDashboard(
|
|
@@ -2384,6 +2820,13 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2384
2820
|
return errorContent("Missing required field: wallet_id");
|
|
2385
2821
|
}
|
|
2386
2822
|
|
|
2823
|
+
const hosted = hostedWalletContext(authInfo);
|
|
2824
|
+
if (hosted) {
|
|
2825
|
+
return content(
|
|
2826
|
+
await hostedListWalletTokens({ ...hosted, walletId: argumentsObject.wallet_id })
|
|
2827
|
+
);
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2387
2830
|
return content(
|
|
2388
2831
|
await requestDashboard(
|
|
2389
2832
|
`/api/agent-wallets/${argumentsObject.wallet_id}/api-keys`,
|
|
@@ -2399,6 +2842,19 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2399
2842
|
return errorContent("Missing required field: wallet_id");
|
|
2400
2843
|
}
|
|
2401
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
|
+
|
|
2402
2858
|
const { wallet_id, ...body } = argumentsObject;
|
|
2403
2859
|
return content(
|
|
2404
2860
|
await requestDashboard(
|
|
@@ -2421,6 +2877,20 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2421
2877
|
return errorContent("Missing required field: key_id");
|
|
2422
2878
|
}
|
|
2423
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
|
+
|
|
2424
2894
|
const { wallet_id, key_id, ...body } = argumentsObject;
|
|
2425
2895
|
return content(
|
|
2426
2896
|
await requestDashboard(
|
|
@@ -2443,6 +2913,17 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2443
2913
|
return errorContent("Missing required field: key_id");
|
|
2444
2914
|
}
|
|
2445
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
|
+
|
|
2446
2927
|
return content(
|
|
2447
2928
|
await requestDashboard(
|
|
2448
2929
|
`/api/agent-wallets/${argumentsObject.wallet_id}/api-keys/${argumentsObject.key_id}`,
|
|
@@ -2482,7 +2963,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2482
2963
|
}
|
|
2483
2964
|
|
|
2484
2965
|
// Force include_qr_data_url so we can append an inline image content
|
|
2485
|
-
// block below
|
|
2966
|
+
// block below, clients that render images (Claude Desktop, MCP
|
|
2486
2967
|
// Inspector) will then show the funding QR right next to the
|
|
2487
2968
|
// newly-created wallet without a follow-up tool call.
|
|
2488
2969
|
created.configure = await buildConfigurePayload({
|
|
@@ -2535,7 +3016,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2535
3016
|
`Token: USDC (${baseUsdcContract})`,
|
|
2536
3017
|
"",
|
|
2537
3018
|
"WARNING: only Base mainnet USDC. Sending from Ethereum, Polygon, Solana,",
|
|
2538
|
-
"or any other network will permanently lose the funds
|
|
3019
|
+
"or any other network will permanently lose the funds, this address is",
|
|
2539
3020
|
"Base only.",
|
|
2540
3021
|
"",
|
|
2541
3022
|
`Block explorer: ${receive.explorer_url}`,
|
|
@@ -2550,7 +3031,7 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
2550
3031
|
|
|
2551
3032
|
// Render the QR inline via MCP image content when the buyer's client
|
|
2552
3033
|
// supports images (Claude Desktop, the MCP Inspector, etc.). Falls back
|
|
2553
|
-
// gracefully on terminals without image support
|
|
3034
|
+
// gracefully on terminals without image support, the text block above
|
|
2554
3035
|
// still has the address and the ANSI QR.
|
|
2555
3036
|
const dataUrl = receive.qr_code_data_url;
|
|
2556
3037
|
if (typeof dataUrl === "string" && dataUrl.startsWith("data:image/png;base64,")) {
|
|
@@ -3030,12 +3511,92 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
3030
3511
|
});
|
|
3031
3512
|
}
|
|
3032
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).
|
|
3033
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) {
|
|
3034
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
|
+
|
|
3035
3593
|
if (name === "apiosk_help") return await handleHelp(argumentsObject);
|
|
3036
3594
|
if (name === "apiosk_payment_guide") return await handlePaymentGuide(argumentsObject, authInfo);
|
|
3037
3595
|
if (name === "apiosk_explore") return await handleExplore(argumentsObject, authInfo);
|
|
3038
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);
|
|
3039
3600
|
if (name === "apiosk_get_api" || name === "apiosk_metadata") {
|
|
3040
3601
|
return await handleGetApi(argumentsObject, authInfo);
|
|
3041
3602
|
}
|
|
@@ -3054,6 +3615,8 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
3054
3615
|
if (name === "apiosk_wallet_reveal_secret") return await handleLocalWalletReveal(argumentsObject);
|
|
3055
3616
|
if (name === "apiosk_wallet_save_secret") return await handleLocalWalletSave(argumentsObject);
|
|
3056
3617
|
|
|
3618
|
+
if (isPublisherTool(name)) return await handlePublisherTool(name, argumentsObject, authInfo, { env });
|
|
3619
|
+
|
|
3057
3620
|
if (name === "apiosk_publish_api") return await handlePublishApi(argumentsObject);
|
|
3058
3621
|
if (name === "apiosk_list_my_apis") return await handleListMyApis(argumentsObject);
|
|
3059
3622
|
if (name === "apiosk_update_api") return await handleUpdateApi(argumentsObject);
|
|
@@ -3080,10 +3643,14 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
3080
3643
|
return await handleDynamicExecute(tool, argumentsObject, authInfo);
|
|
3081
3644
|
} catch (error) {
|
|
3082
3645
|
if (error instanceof ApioskPaymentRequiredError) {
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
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),
|
|
3087
3654
|
payment_required: error.paymentRequired,
|
|
3088
3655
|
});
|
|
3089
3656
|
}
|
|
@@ -3095,7 +3662,19 @@ export function createApioskMcpRuntime(options = {}) {
|
|
|
3095
3662
|
}
|
|
3096
3663
|
|
|
3097
3664
|
return {
|
|
3098
|
-
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
|
+
},
|
|
3099
3678
|
isToolProtected,
|
|
3100
3679
|
callTool,
|
|
3101
3680
|
};
|