@apiosk/mcp 1.3.1 → 2.0.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 +84 -537
- package/assets/brand/apiosk-a-20260921.png +0 -0
- package/assets/brand/apple-touch-icon.png +0 -0
- package/assets/brand/favicon-dark.ico +0 -0
- package/assets/brand/favicon-light.ico +0 -0
- package/assets/brand/favicon.ico +0 -0
- package/assets/brand/icon-192.png +0 -0
- package/assets/brand/icon-512.png +0 -0
- package/assets/brand/icon-maskable-512.png +0 -0
- package/assets/brand/inter-OFL.txt +93 -0
- package/assets/brand/inter-latin-400-normal.woff2 +0 -0
- package/assets/brand/inter-latin-500-normal.woff2 +0 -0
- package/assets/brand/inter-latin-600-normal.woff2 +0 -0
- package/assets/brand/mark-20260905-transparent.svg +1 -0
- package/assets/brand/mark-20260918.svg +1 -0
- package/assets/brand/mark-black-20260918.svg +1 -0
- package/assets/brand/mark-dark-20260905-transparent.png +0 -0
- package/assets/brand/mark-dark-20260918.png +0 -0
- package/assets/brand/mark-dark-96.png +0 -0
- package/assets/brand/mark-light-20260905-transparent.png +0 -0
- package/assets/brand/mark-light-20260918.png +0 -0
- package/assets/brand/mark-light-96.png +0 -0
- package/assets/brand/wordmark-black-320.png +0 -0
- package/assets/brand/wordmark-white-320.png +0 -0
- package/docs/branding.md +11 -0
- package/docs/marketplace-submission-2026-04-08.md +4 -0
- package/docs/marketplace-submission-2026-08-20.md +157 -0
- package/docs/openai-plugin-submission-2026-09-05.md +143 -0
- package/docs/sepa-rail.md +13 -13
- package/dxt.json +26 -22
- package/index.mjs +3 -1
- package/logo-optimized-light.png +0 -0
- package/package.json +15 -13
- package/plugin/apiosk/.codex-plugin/plugin.json +45 -0
- package/plugin/apiosk/.mcp.json +8 -0
- package/plugin/apiosk/assets/icon-dark.png +0 -0
- package/plugin/apiosk/assets/icon.png +0 -0
- package/plugin/apiosk/assets/icon.svg +1 -0
- package/plugin/apiosk/assets/logo.png +0 -0
- package/plugin/apiosk/skills/apiosk/SKILL.md +33 -0
- package/plugin/apiosk/skills/apiosk/agents/openai.yaml +12 -0
- package/server.json +98 -9
- package/server.mjs +258 -71
- package/src/approval-feedback.mjs +21 -0
- package/src/brand-routes.mjs +28 -0
- package/src/create-server.mjs +168 -9
- package/src/display-money.mjs +20 -0
- package/src/display-text.mjs +67 -0
- package/src/gateway-client.mjs +42 -0
- package/src/gateway-v2-ask.mjs +50 -0
- package/src/gateway-v2-card-account.mjs +23 -0
- package/src/gateway-v2-card-actions.mjs +67 -0
- package/src/gateway-v2-card-answer-text.mjs +130 -0
- package/src/gateway-v2-card-answer.mjs +68 -0
- package/src/gateway-v2-card-blocks.mjs +166 -0
- package/src/gateway-v2-card-body.mjs +259 -0
- package/src/gateway-v2-card-budget.mjs +21 -0
- package/src/gateway-v2-card-cbs.mjs +56 -0
- package/src/gateway-v2-card-choices.mjs +4 -0
- package/src/gateway-v2-card-clarification.mjs +25 -0
- package/src/gateway-v2-card-compact.mjs +34 -0
- package/src/gateway-v2-card-events.mjs +41 -0
- package/src/gateway-v2-card-presentation.mjs +125 -0
- package/src/gateway-v2-card-research.mjs +63 -0
- package/src/gateway-v2-card-result.mjs +32 -0
- package/src/gateway-v2-card-search.mjs +39 -0
- package/src/gateway-v2-card-sources.mjs +32 -0
- package/src/gateway-v2-card-style.mjs +76 -0
- package/src/gateway-v2-card-verdict.mjs +66 -0
- package/src/gateway-v2-card.mjs +91 -0
- package/src/gateway-v2-contracts.json +66 -0
- package/src/gateway-v2-instructions.md +113 -0
- package/src/gateway-v2-recovery.mjs +18 -0
- package/src/gateway-v2-report-links.mjs +14 -0
- package/src/gateway-v2-workflows.mjs +17 -0
- package/src/gateway-v2.mjs +179 -0
- package/src/oauth.mjs +603 -364
- package/src/observability.mjs +210 -0
- package/src/result-presentation.mjs +9 -0
- package/src/runtime.mjs +24 -3091
- package/src/settlement-disclosure.mjs +26 -0
- package/src/source-groups.mjs +91 -0
- package/src/source-value-format.mjs +25 -0
- package/src/tool-result.mjs +20 -0
- package/src/ui-bridge.mjs +185 -0
- package/src/well-known-routes.mjs +132 -0
- package/src/funding-options.mjs +0 -255
- package/src/gateway-management.mjs +0 -107
- package/src/listing-metadata.mjs +0 -287
- package/src/local-config.mjs +0 -256
- package/src/payment-guidance.mjs +0 -307
- package/src/wallet-store.mjs +0 -476
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// MCP observability side-car — the MCP server historically logged NOTHING about
|
|
2
|
+
// its own activity (tool calls, SSE sessions, OAuth/installs). This module writes
|
|
3
|
+
// append-only rows to the mcp_tool_calls / mcp_sessions / mcp_oauth_events tables
|
|
4
|
+
// (gateway migration 057) so the admin portal can see MCP traffic.
|
|
5
|
+
//
|
|
6
|
+
// Design: fire-and-forget, NEVER throws into the hot path (every write is wrapped
|
|
7
|
+
// in try/catch and returns a swallowed promise). Uses the service-role key exactly
|
|
8
|
+
// like publisher.mjs (rest/v1/<table>). PRIVACY: raw connect tokens are sha256-hashed
|
|
9
|
+
// (never stored raw), and only argument KEY NAMES are stored — never values, headers,
|
|
10
|
+
// or bodies.
|
|
11
|
+
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
|
|
14
|
+
const DEFAULT_SUPABASE_URL = "https://api.apiosk.com";
|
|
15
|
+
const physicalSchemaOrigins = new Set();
|
|
16
|
+
|
|
17
|
+
function resolveConfig(env = {}) {
|
|
18
|
+
const raw =
|
|
19
|
+
env.APIOSK_SUPABASE_URL || env.SUPABASE_URL || DEFAULT_SUPABASE_URL;
|
|
20
|
+
const url = String(raw).replace(/\/+$/, "");
|
|
21
|
+
const key =
|
|
22
|
+
env.APIOSK_SUPABASE_SERVICE_ROLE_KEY || env.SUPABASE_SERVICE_ROLE_KEY || "";
|
|
23
|
+
return { url, key };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sha256(value) {
|
|
27
|
+
if (!value || typeof value !== "string") return null;
|
|
28
|
+
try {
|
|
29
|
+
return createHash("sha256").update(value).digest("hex");
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function trimStr(v, max = 400) {
|
|
36
|
+
if (v == null) return null;
|
|
37
|
+
const s = String(v);
|
|
38
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Low-level fire-and-forget REST write. Resolves regardless of outcome; a failure
|
|
42
|
+
// is logged to stderr but never propagates — observability must not break a tool call.
|
|
43
|
+
async function restWrite(env, path, body, { method = "POST", extraHeaders = {} } = {}) {
|
|
44
|
+
const { url, key } = resolveConfig(env);
|
|
45
|
+
if (!key) return; // not configured on this deployment — skip silently
|
|
46
|
+
const fetchImpl = globalThis.fetch;
|
|
47
|
+
if (typeof fetchImpl !== "function") return;
|
|
48
|
+
try {
|
|
49
|
+
const physical = physicalSchemaOrigins.has(url);
|
|
50
|
+
const options = {
|
|
51
|
+
method,
|
|
52
|
+
headers: {
|
|
53
|
+
apikey: key,
|
|
54
|
+
authorization: `Bearer ${key}`,
|
|
55
|
+
"content-type": "application/json",
|
|
56
|
+
prefer: "return=minimal",
|
|
57
|
+
...(physical ? { "Accept-Profile": "agents", "Content-Profile": "agents" } : {}),
|
|
58
|
+
...extraHeaders,
|
|
59
|
+
},
|
|
60
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
61
|
+
};
|
|
62
|
+
const response = await fetchImpl(`${url}/rest/v1/${path}`, options);
|
|
63
|
+
// A lookup failure means no SQL ran; never retry an ambiguous write.
|
|
64
|
+
if (response?.status === 404 && (await response.clone().json()).code === "PGRST205") {
|
|
65
|
+
const schema = physical ? "public" : "agents";
|
|
66
|
+
const retried = await fetchImpl(`${url}/rest/v1/${path}`, {
|
|
67
|
+
...options,
|
|
68
|
+
headers: { ...options.headers, "Accept-Profile": schema, "Content-Profile": schema },
|
|
69
|
+
});
|
|
70
|
+
if (retried.ok) {
|
|
71
|
+
if (physical) physicalSchemaOrigins.delete(url);
|
|
72
|
+
else physicalSchemaOrigins.add(url);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
try {
|
|
77
|
+
console.warn(
|
|
78
|
+
"[observability] write failed:",
|
|
79
|
+
error && error.message ? error.message : String(error),
|
|
80
|
+
);
|
|
81
|
+
} catch {
|
|
82
|
+
/* ignore */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Extract caller identity from the MCP authInfo (never returns a raw token). */
|
|
88
|
+
export function callerFrom(authInfo) {
|
|
89
|
+
const x = (authInfo && (authInfo.extra || authInfo)) || {};
|
|
90
|
+
const rawToken = x.apiosk_connect_token || x.connectToken || null;
|
|
91
|
+
const hasToken = Boolean(rawToken || x.apiosk_connect_token_id);
|
|
92
|
+
return {
|
|
93
|
+
auth_method: hasToken ? "connect_token" : x.userId ? "oauth" : "anonymous",
|
|
94
|
+
user_id: x.userId || x.user_id || null,
|
|
95
|
+
connect_token_id: x.apiosk_connect_token_id || x.connect_token_id || null,
|
|
96
|
+
connect_token_hash: sha256(rawToken),
|
|
97
|
+
wallet_address:
|
|
98
|
+
x.walletAddress || x.apiosk_connect_wallet_address || x.wallet_address || null,
|
|
99
|
+
provider_id: x.providerId || x.provider_id || null,
|
|
100
|
+
client_name: (authInfo && (authInfo.clientName || authInfo.client_name)) || null,
|
|
101
|
+
client_kind: (authInfo && (authInfo.clientKind || authInfo.client_kind)) || null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Log one tools/call dispatch. Fire-and-forget. */
|
|
106
|
+
export function logToolCall(
|
|
107
|
+
env,
|
|
108
|
+
{
|
|
109
|
+
toolName,
|
|
110
|
+
outcome = "ok",
|
|
111
|
+
errorCode = null,
|
|
112
|
+
latencyMs = null,
|
|
113
|
+
authInfo = null,
|
|
114
|
+
argKeys = [],
|
|
115
|
+
sessionId = null,
|
|
116
|
+
gatewayRequestId = null,
|
|
117
|
+
ip = null,
|
|
118
|
+
userAgent = null,
|
|
119
|
+
} = {},
|
|
120
|
+
) {
|
|
121
|
+
const caller = callerFrom(authInfo);
|
|
122
|
+
return restWrite(env, "mcp_tool_calls", {
|
|
123
|
+
tool_name: String(toolName || "unknown"),
|
|
124
|
+
outcome,
|
|
125
|
+
error_code: errorCode ? trimStr(errorCode, 120) : null,
|
|
126
|
+
latency_ms: typeof latencyMs === "number" ? Math.round(latencyMs) : null,
|
|
127
|
+
...caller,
|
|
128
|
+
session_id: sessionId,
|
|
129
|
+
gateway_request_id: gatewayRequestId,
|
|
130
|
+
arg_keys: Array.isArray(argKeys) ? argKeys.slice(0, 64).map(String) : [],
|
|
131
|
+
ip_address: ip,
|
|
132
|
+
user_agent: trimStr(userAgent),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Record an SSE session on connect (upsert on session_id). Fire-and-forget. */
|
|
137
|
+
export function openSession(
|
|
138
|
+
env,
|
|
139
|
+
{ sessionId, transport = "sse", ip = null, userAgent = null, clientName = null, clientKind = null, protocolVersion = null } = {},
|
|
140
|
+
) {
|
|
141
|
+
if (!sessionId) return;
|
|
142
|
+
return restWrite(
|
|
143
|
+
env,
|
|
144
|
+
"mcp_sessions",
|
|
145
|
+
{
|
|
146
|
+
session_id: String(sessionId),
|
|
147
|
+
transport,
|
|
148
|
+
ip_address: ip,
|
|
149
|
+
user_agent: trimStr(userAgent),
|
|
150
|
+
client_name: clientName,
|
|
151
|
+
client_kind: clientKind,
|
|
152
|
+
protocol_version: protocolVersion,
|
|
153
|
+
status: "online",
|
|
154
|
+
},
|
|
155
|
+
// Upsert: a reconnect with the same id refreshes it instead of 409-ing.
|
|
156
|
+
{ extraHeaders: { prefer: "resolution=merge-duplicates,return=minimal" } },
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Mark an SSE session closed on disconnect. Fire-and-forget. */
|
|
161
|
+
export function closeSession(env, sessionId) {
|
|
162
|
+
if (!sessionId) return;
|
|
163
|
+
const now = new Date().toISOString();
|
|
164
|
+
return restWrite(
|
|
165
|
+
env,
|
|
166
|
+
`mcp_sessions?session_id=eq.${encodeURIComponent(String(sessionId))}`,
|
|
167
|
+
{ status: "closed", disconnected_at: now, last_activity_at: now },
|
|
168
|
+
{ method: "PATCH" },
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Log an OAuth / install event (authorize, consent, token_issued, wallet_created…). */
|
|
173
|
+
export function logOAuthEvent(
|
|
174
|
+
env,
|
|
175
|
+
{
|
|
176
|
+
eventType,
|
|
177
|
+
userId = null,
|
|
178
|
+
clientId = null,
|
|
179
|
+
clientName = null,
|
|
180
|
+
redirectUri = null,
|
|
181
|
+
scopes = [],
|
|
182
|
+
connectTokenId = null,
|
|
183
|
+
connectTokenHash = null,
|
|
184
|
+
connectTokenRaw = null,
|
|
185
|
+
walletAddress = null,
|
|
186
|
+
walletCreated = false,
|
|
187
|
+
outcome = "ok",
|
|
188
|
+
errorCode = null,
|
|
189
|
+
ip = null,
|
|
190
|
+
userAgent = null,
|
|
191
|
+
} = {},
|
|
192
|
+
) {
|
|
193
|
+
if (!eventType) return;
|
|
194
|
+
return restWrite(env, "mcp_oauth_events", {
|
|
195
|
+
event_type: eventType,
|
|
196
|
+
user_id: userId,
|
|
197
|
+
client_id: clientId ? trimStr(clientId, 200) : null,
|
|
198
|
+
client_name: clientName,
|
|
199
|
+
redirect_uri: redirectUri ? trimStr(redirectUri, 500) : null,
|
|
200
|
+
scopes: Array.isArray(scopes) ? scopes.map(String) : [],
|
|
201
|
+
connect_token_id: connectTokenId,
|
|
202
|
+
connect_token_hash: connectTokenHash || sha256(connectTokenRaw),
|
|
203
|
+
wallet_address: walletAddress,
|
|
204
|
+
wallet_created: Boolean(walletCreated),
|
|
205
|
+
outcome,
|
|
206
|
+
error_code: errorCode ? trimStr(errorCode, 120) : null,
|
|
207
|
+
ip_address: ip,
|
|
208
|
+
user_agent: trimStr(userAgent),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Shared by the tool response and the card's completion notification.
|
|
2
|
+
export const V2_SOURCES_PRESENTATION = "Source overview presentation: the interactive Apiosk card already displays the sources, total, filters, pagination and expandable services. When the card is displayed, add at most one short confirmation sentence. Do not repeat the sources as a second table, list, category breakdown or readiness report underneath or above the card. 'Show sources', 'list all sources' and 'which sources are available' request this one card, not a duplicate text overview. Only provide a separate text table or export when the actual user explicitly requests that format. If the host cannot display the card, provide one compact text overview from the returned sources and respect pagination. Count Pulse Network once and keep its services nested. Show only supported function counts; never display registered endpoint totals or supported/registered ratios in a source overview. Use this fresh result's total; never repeat an older catalog count from conversation history. Do not append an unsolicited question or analysis.";
|
|
3
|
+
|
|
4
|
+
export const V2_RESULT_TOOL_DESCRIPTION = "Use English for Apiosk workflow messages, unless a translation is explicitly requested. Display Apiosk prices, charges and balances in the account display currency from context_view.money_display, using the converted prices supplied in the response. Raw proposal and billing amounts remain micro USD for authorization; never present those raw amounts as the preferred currency or override EUR with USD. Never change source document currencies. Output: this tool displays the complete result in an interactive card. After it, write only a brief confirmation (entity, source, period/date and optional unit caveat) and source citation. Do not add a second table, figures, costs, missing-data list or analysis unless the user explicitly requests those details. 'Show the result' or 'retrieve annual accounts' means display the card, not write a second report.";
|
|
5
|
+
export const V2_RESULT_PRESENTATION = "Use the converted Apiosk prices and account display currency supplied by this response, including EUR when preferred. Raw billing and approval micro USD amounts are internal accounting values, not the user display currency. Preserve source document figures and currencies. Display all-uppercase company names in readable title case, for example ORION BEHEER BV as Orion Beheer BV; preserve legal abbreviations such as BV/NV and mixed-case brands. Keep identifiers, URLs and raw source JSON exact. When the interactive Apiosk result card is visible, the default reply after it is only 1–3 short sentences plus a source citation: identify what was retrieved, the entity, source, covered period and relevant source dates. A short currency/unit caveat is allowed when relevant. Do not repeat the card's figures, tables, JSON, charges, fees, balance or detailed missing-data lists. Give those details or analysis only when the actual user explicitly asks for them; a request to fetch or show records alone is not such a request. Use only returned evidence. If no result card can be displayed, provide the requested source-backed answer in text.";
|
|
6
|
+
|
|
7
|
+
export const V2_VERIFICATION_PRESENTATION = "For a supplier verification or validity question, answer the question with a concise supported conclusion and the material unknowns, not merely 'results retrieved'. Distinguish active legal registration from complete onboarding clearance. If ownership or VAT is unverified, say not fully verified for onboarding, not that the company is invalid. Screening candidates require identity review and are not confirmed sanctions findings. Do not treat no matches or a completed task as a compliance certificate. Source records are available in the card's collapsed Sources and details section.";
|
|
8
|
+
|
|
9
|
+
export const V2_RESULT_READY_PROMPT = "The Apiosk answer is now visible above collapsed source details. Add only a brief conclusion and source citation below it. Do not generate a second detailed answer automatically. " + V2_RESULT_PRESENTATION + " " + V2_VERIFICATION_PRESENTATION + " Do not purchase the same data again.";
|