@klappay/mcp 0.0.0-stage → 1.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/LICENSE +21 -0
- package/README.md +132 -2
- package/dist/chunk-FCIY5UOM.js +9 -0
- package/dist/chunk-FCIY5UOM.js.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/start-4SOOSFNC.js +620 -0
- package/dist/start-4SOOSFNC.js.map +1 -0
- package/docs/configuration.md +79 -0
- package/docs/getting-started.md +80 -0
- package/docs/public/favicon.png +0 -0
- package/docs/public/logo.png +0 -0
- package/docs/security.md +66 -0
- package/docs/tools.md +131 -0
- package/package.json +69 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/start.ts","../src/environment.ts","../src/redact.ts","../src/server.ts","../package.json","../src/tool-policy.ts","../src/tools/charges.ts","../src/ids.ts","../src/result.ts","../src/tools/charge-output.ts","../src/tools/metrics.ts","../src/tools/networks.ts","../src/tools/sandbox.ts","../src/tools/status.ts","../src/tools/webhooks.ts"],"sourcesContent":["import { loadCredentials } from '@klappay/cli/credentials'\nimport { createClient } from '@klappay/node'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { resolveServerEnvironment } from './environment'\nimport { hostOf } from './redact'\nimport { createServer } from './server'\nimport { SERVER_NAME } from './server-name'\n\nexport async function start(): Promise<void> {\n const resolved = await resolveServerEnvironment(\n {\n KLAP_ENV: process.env.KLAP_ENV,\n KLAP_API_KEY: process.env.KLAP_API_KEY,\n KLAP_BASE_URL: process.env.KLAP_BASE_URL,\n KLAP_MCP_ALLOW_LIVE_WRITES: process.env.KLAP_MCP_ALLOW_LIVE_WRITES,\n },\n loadCredentials,\n )\n if (!resolved.ok) {\n console.error(`${SERVER_NAME}: ${resolved.message}`)\n process.exit(1)\n }\n\n const { apiKey, baseUrl, environment, allowLiveWrites } = resolved\n const client = createClient({ apiKey, baseUrl })\n const server = createServer({ client, environment, baseUrl, allowLiveWrites })\n await server.connect(new StdioServerTransport())\n\n const writes = environment === 'live' && allowLiveWrites ? ' (live writes enabled)' : ''\n console.error(`${SERVER_NAME}: ${environment} → ${hostOf(baseUrl)}${writes}`)\n}\n","import {\n CREDENTIALS_DISPLAY_PATH,\n type KlapCredentialsConfig,\n KlapCredentialsError,\n type KlapCredentialsErrorCode,\n type KlapEnvironment,\n detectEnvironment,\n} from '@klappay/cli/credentials'\n\nexport type ServerEnvironmentVars = {\n KLAP_ENV?: string\n KLAP_API_KEY?: string\n KLAP_BASE_URL?: string\n KLAP_MCP_ALLOW_LIVE_WRITES?: string\n}\n\nexport type KeySource = 'env' | 'config'\n\nexport type ResolvedServerEnvironment = {\n ok: true\n apiKey: string\n baseUrl: string\n environment: KlapEnvironment\n allowLiveWrites: boolean\n keySource: KeySource\n}\n\nexport type ServerEnvironmentRefusal = { ok: false; message: string }\n\nexport type ServerEnvironmentResult = ResolvedServerEnvironment | ServerEnvironmentRefusal\n\nexport type LoadCredentialsConfig = () => Promise<KlapCredentialsConfig | null>\n\nconst LOOPBACK_HOSTNAMES = ['localhost', '127.0.0.1', '[::1]']\nconst LIVE_WRITES_ENABLED_VALUE = '1'\nconst LOGIN_COMMAND = '`klap login --api-key - --base-url <url>`'\nconst LOGIN_HINT = `Run ${LOGIN_COMMAND} (from @klappay/cli) or set KLAP_API_KEY and KLAP_BASE_URL.`\n\nconst CREDENTIALS_ERROR_MESSAGES: Record<KlapCredentialsErrorCode, string> = {\n invalid_api_key_prefix: `A key in ${CREDENTIALS_DISPLAY_PATH} does not start with klap_test_ or klap_live_. Run ${LOGIN_COMMAND} again.`,\n invalid_credentials_file: `${CREDENTIALS_DISPLAY_PATH} is corrupted or has a key in the wrong environment slot. Run \\`klap logout\\` to remove it, then ${LOGIN_COMMAND} again.`,\n credentials_path_symlink: `Refusing to read ${CREDENTIALS_DISPLAY_PATH}: it (or ~/.klap) is a symbolic link. Remove the link, then run ${LOGIN_COMMAND} to create a real ~/.klap directory.`,\n no_credentials: `No API key in ${CREDENTIALS_DISPLAY_PATH}. ${LOGIN_HINT}`,\n missing_environment_key: `${CREDENTIALS_DISPLAY_PATH} has no key for the requested environment. Run ${LOGIN_COMMAND} with that key, or change KLAP_ENV.`,\n ambiguous_environment: `${CREDENTIALS_DISPLAY_PATH} has keys for both environments; set KLAP_ENV to test or live.`,\n}\n\nfunction credentialsErrorMessage(err: KlapCredentialsError): string {\n if (Object.hasOwn(CREDENTIALS_ERROR_MESSAGES, err.code))\n return CREDENTIALS_ERROR_MESSAGES[err.code]\n return `Could not use ${CREDENTIALS_DISPLAY_PATH} (${err.code}). Run \\`klap logout\\`, then ${LOGIN_COMMAND} again.`\n}\n\nfunction refuse(message: string): ServerEnvironmentRefusal {\n return { ok: false, message }\n}\n\nfunction presentValue(value: string | undefined): string | undefined {\n return value === undefined || value === '' ? undefined : value\n}\n\nfunction parseEnvironment(value: string | undefined): KlapEnvironment | null {\n if (value === undefined) return 'test'\n if (value === 'test' || value === 'live') return value\n return null\n}\n\nfunction keyEnvironment(apiKey: string): KlapEnvironment | null {\n try {\n return detectEnvironment(apiKey)\n } catch {\n return null\n }\n}\n\nfunction baseUrlProblem(value: string, label: string): string | null {\n let url: URL\n try {\n url = new URL(value)\n } catch {\n return `${label} is not a valid URL.`\n }\n if (url.username !== '' || url.password !== '') {\n return `${label} must not contain a username or password.`\n }\n if (url.protocol === 'https:') return null\n if (url.protocol === 'http:' && LOOPBACK_HOSTNAMES.includes(url.hostname)) return null\n return `${label} must use https:// (http:// is only allowed for localhost, 127.0.0.1 or [::1]).`\n}\n\nfunction normalizedBaseUrl(value: string): string {\n const url = new URL(value)\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}`\n}\n\nfunction keyMismatchMessage(source: string, keyEnv: KlapEnvironment, wanted: KlapEnvironment) {\n return `${source} is a ${keyEnv} key, but the server is set to ${wanted} (KLAP_ENV${wanted === 'test' ? ' unset or test' : '=live'}). Refusing to start.`\n}\n\nfunction resolveFromEnvKey(\n apiKey: string,\n baseUrl: string | undefined,\n environment: KlapEnvironment,\n allowLiveWrites: boolean,\n): ServerEnvironmentResult {\n if (baseUrl === undefined) {\n return refuse('KLAP_API_KEY is set but KLAP_BASE_URL is not; set both.')\n }\n const urlProblem = baseUrlProblem(baseUrl, 'KLAP_BASE_URL')\n if (urlProblem) return refuse(urlProblem)\n\n const keyEnv = keyEnvironment(apiKey)\n if (keyEnv === null) return refuse('KLAP_API_KEY must start with klap_test_ or klap_live_.')\n if (keyEnv !== environment) return refuse(keyMismatchMessage('KLAP_API_KEY', keyEnv, environment))\n\n return { ok: true, apiKey, baseUrl, environment, allowLiveWrites, keySource: 'env' }\n}\n\nasync function loadConfigOrRefusal(\n loadConfig: LoadCredentialsConfig,\n): Promise<KlapCredentialsConfig | ServerEnvironmentRefusal> {\n try {\n const config = await loadConfig()\n return config ?? refuse(`No ${CREDENTIALS_DISPLAY_PATH} found. ${LOGIN_HINT}`)\n } catch (err) {\n if (err instanceof KlapCredentialsError) return refuse(credentialsErrorMessage(err))\n const code = err instanceof Error && 'code' in err ? String(err.code) : 'unknown error'\n return refuse(`Could not read ${CREDENTIALS_DISPLAY_PATH} (${code}).`)\n }\n}\n\nasync function resolveFromConfig(\n loadConfig: LoadCredentialsConfig,\n overrideBaseUrl: string | undefined,\n environment: KlapEnvironment,\n allowLiveWrites: boolean,\n): Promise<ServerEnvironmentResult> {\n const loaded = await loadConfigOrRefusal(loadConfig)\n if ('ok' in loaded) return loaded\n\n const apiKey = loaded.apiKeys[environment]\n if (apiKey === undefined || apiKey === '') {\n return refuse(\n `${CREDENTIALS_DISPLAY_PATH} has no ${environment} key. Run ${LOGIN_COMMAND} with a klap_${environment}_ key, or set KLAP_ENV to the environment you have a key for.`,\n )\n }\n const keyEnv = keyEnvironment(apiKey)\n if (keyEnv !== environment) {\n return refuse(\n `The ${environment} slot in ${CREDENTIALS_DISPLAY_PATH} does not hold a klap_${environment}_ key. Run ${LOGIN_COMMAND} again.`,\n )\n }\n\n const urlProblem = baseUrlProblem(loaded.baseUrl, `The baseUrl in ${CREDENTIALS_DISPLAY_PATH}`)\n if (urlProblem) return refuse(urlProblem)\n\n if (overrideBaseUrl !== undefined) {\n const overrideProblem = baseUrlProblem(overrideBaseUrl, 'KLAP_BASE_URL')\n if (overrideProblem) return refuse(overrideProblem)\n if (normalizedBaseUrl(overrideBaseUrl) !== normalizedBaseUrl(loaded.baseUrl)) {\n return refuse(\n `KLAP_BASE_URL differs from the baseUrl stored in ${CREDENTIALS_DISPLAY_PATH}. A stored key is only ever sent to the base URL it was saved with — unset KLAP_BASE_URL, or set KLAP_API_KEY together with KLAP_BASE_URL instead.`,\n )\n }\n }\n\n return {\n ok: true,\n apiKey,\n baseUrl: loaded.baseUrl,\n environment,\n allowLiveWrites,\n keySource: 'config',\n }\n}\n\nexport async function resolveServerEnvironment(\n vars: ServerEnvironmentVars,\n loadConfig: LoadCredentialsConfig,\n): Promise<ServerEnvironmentResult> {\n const environment = parseEnvironment(presentValue(vars.KLAP_ENV))\n if (environment === null) return refuse('KLAP_ENV must be unset, \"test\" or \"live\".')\n\n const allowLiveWrites = vars.KLAP_MCP_ALLOW_LIVE_WRITES === LIVE_WRITES_ENABLED_VALUE\n const apiKey = presentValue(vars.KLAP_API_KEY)\n const baseUrl = presentValue(vars.KLAP_BASE_URL)\n\n if (apiKey !== undefined) {\n return resolveFromEnvKey(apiKey, baseUrl, environment, allowLiveWrites)\n }\n return resolveFromConfig(loadConfig, baseUrl, environment, allowLiveWrites)\n}\n","export const REDACTED_URL = '[unparseable url]'\n\nexport function redactUrl(value: string): string {\n let url: URL\n try {\n url = new URL(value)\n } catch {\n return REDACTED_URL\n }\n if (url.origin === 'null') return REDACTED_URL\n return `${url.origin}${url.pathname}`\n}\n\nexport function hostOf(baseUrl: string): string {\n try {\n return new URL(baseUrl).host\n } catch {\n return REDACTED_URL\n }\n}\n","import type { KlapEnvironment } from '@klappay/cli/credentials'\nimport type { KlapClient } from '@klappay/node'\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { version } from '../package.json'\nimport { hostOf } from './redact'\nimport { isToolAllowed } from './tool-policy'\nimport { chargeTools } from './tools/charges'\nimport type { ToolDefinition } from './tools/definition'\nimport { metricsTools } from './tools/metrics'\nimport { networkTools } from './tools/networks'\nimport { sandboxTools } from './tools/sandbox'\nimport { statusTools } from './tools/status'\nimport { webhookTools } from './tools/webhooks'\n\nimport { SERVER_NAME } from './server-name'\n\nexport const ALL_TOOLS: readonly ToolDefinition[] = [\n ...statusTools,\n ...chargeTools,\n ...webhookTools,\n ...networkTools,\n ...metricsTools,\n ...sandboxTools,\n]\n\nexport type CreateServerOptions = {\n client: KlapClient\n environment: KlapEnvironment\n baseUrl: string\n allowLiveWrites: boolean\n}\n\nfunction modeDescription(environment: KlapEnvironment, allowLiveWrites: boolean): string {\n if (environment === 'test') {\n return 'This is the TEST environment: sandbox data and no real funds. Read tools, charges_create, charges_check, webhooks_retry_delivery and sandbox_trigger are available.'\n }\n if (allowLiveWrites) {\n return 'This is the LIVE environment: real merchants, real payers and real funds. Live writes were explicitly enabled (KLAP_MCP_ALLOW_LIVE_WRITES=1): charges_create, charges_check and webhooks_retry_delivery act on real data — confirm with the user before calling them.'\n }\n return 'This is the LIVE environment: real merchants, real payers and real funds. The server is read-only in live; write tools are not registered unless KLAP_MCP_ALLOW_LIVE_WRITES=1 is set.'\n}\n\nexport function buildInstructions(\n environment: KlapEnvironment,\n baseUrl: string,\n allowLiveWrites: boolean,\n): string {\n return [\n `Klap Core API (${hostOf(baseUrl)}), environment: ${environment}.`,\n modeDescription(environment, allowLiveWrites),\n 'Moving money out of a charge (escrow release/refund) and managing webhook endpoints or payout recipients are not available through this server.',\n ].join(' ')\n}\n\nexport function createServer(options: CreateServerOptions): McpServer {\n const { client, environment, baseUrl, allowLiveWrites } = options\n const server = new McpServer(\n { name: SERVER_NAME, version },\n { instructions: buildInstructions(environment, baseUrl, allowLiveWrites) },\n )\n for (const tool of ALL_TOOLS) {\n if (isToolAllowed(tool.access, environment, allowLiveWrites)) {\n tool.register(server, { client, environment, baseUrl })\n }\n }\n return server\n}\n","{\n \"name\": \"@klappay/mcp\",\n \"version\": \"1.0.0\",\n \"license\": \"MIT\",\n \"description\": \"Local stdio MCP server for the Klap Core API — lets an AI assistant read charges, webhooks, networks and metrics, and (in test mode) create charges and simulate sandbox events, using the key `klap login` already stored on your machine.\",\n \"keywords\": [\"klap\", \"klappay\", \"mcp\", \"model-context-protocol\", \"crypto\", \"payments\", \"web3\"],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/klappay/klap-mcp.git\"\n },\n \"engines\": {\n \"node\": \">=24\"\n },\n \"packageManager\": \"pnpm@10.33.0\",\n \"type\": \"module\",\n \"bin\": {\n \"klap-mcp\": \"./dist/index.js\"\n },\n \"files\": [\n \"dist\",\n \"LICENSE\",\n \"README.md\",\n \"docs\",\n \"!docs/.vitepress\",\n \"!docs/index.md\",\n \"!docs/public/CNAME\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"format\": \"biome format --write .\",\n \"test\": \"vitest run\",\n \"docs:dev\": \"vitepress dev docs\",\n \"docs:build\": \"vitepress build docs\",\n \"docs:preview\": \"vitepress preview docs\",\n \"changeset\": \"changeset\",\n \"version\": \"changeset version\",\n \"release\": \"pnpm build && changeset publish\",\n \"prepare\": \"husky\"\n },\n \"dependencies\": {\n \"@klappay/cli\": \"^1.4.0\",\n \"@klappay/node\": \"^5.1.4\",\n \"@klappay/types\": \"6.0.0\",\n \"@modelcontextprotocol/sdk\": \"1.32.1\",\n \"zod\": \"3.25.76\"\n },\n \"devDependencies\": {\n \"@biomejs/biome\": \"^1.9.0\",\n \"@changesets/cli\": \"^2.31.1\",\n \"@commitlint/cli\": \"^19.5.0\",\n \"@commitlint/config-conventional\": \"^19.5.0\",\n \"@types/node\": \"^20.16.0\",\n \"husky\": \"^9.1.0\",\n \"lint-staged\": \"^15.2.0\",\n \"tsup\": \"^8.3.0\",\n \"typescript\": \"^5.6.0\",\n \"vitepress\": \"^1.6.4\",\n \"vitepress-plugin-llms\": \"^1.14.0\",\n \"vitest\": \"^3.2.6\"\n },\n \"pnpm\": {\n \"onlyBuiltDependencies\": [\"@biomejs/biome\", \"esbuild\"]\n }\n}\n","import type { KlapEnvironment } from '@klappay/cli/credentials'\nimport type { ToolAccess } from './tools/definition'\n\nexport function isToolAllowed(\n access: ToolAccess,\n environment: KlapEnvironment,\n allowLiveWrites: boolean,\n): boolean {\n if (environment === 'test') return true\n if (access === 'read') return true\n if (access === 'write') return allowLiveWrites\n return false\n}\n","import {\n ChargeSchema,\n CheckChargeRequestSchema,\n CheckChargeResponseSchema,\n CreateChargeSchema,\n TimelineEventSchema,\n} from '@klappay/types'\nimport { z } from 'zod'\nimport { ChargeIdSchema } from '../ids'\nimport { ToolInputError, runTool } from '../result'\nimport { ChargeWithoutMetadataSchema } from './charge-output'\nimport type { ToolDefinition } from './definition'\n\nconst CheckResponseWithoutMetadataSchema = CheckChargeResponseSchema.omit({ metadata: true })\nconst TimelineSchema = z.array(TimelineEventSchema)\n\nexport const CreateChargeToolInputSchema = CreateChargeSchema.innerType()\n .omit({ escrow: true, redirectUrl: true })\n .strict()\n\nconst GetChargeInputSchema = z\n .object({\n id: ChargeIdSchema,\n includeMetadata: z\n .boolean()\n .default(false)\n .describe(\n \"Include the charge's free-form `metadata` object (merchant-supplied data, may hold customer details). Omitted unless true.\",\n ),\n })\n .strict()\n\nconst ChargeIdInputSchema = z.object({ id: ChargeIdSchema }).strict()\n\nconst CheckChargeInputSchema = CheckChargeRequestSchema.innerType()\n .extend({ id: ChargeIdSchema })\n .strict()\n\nfunction validationMessage(error: z.ZodError): string {\n return error.issues.map((issue) => issue.message).join('; ')\n}\n\nexport const chargeTools: ToolDefinition[] = [\n {\n name: 'charges_get',\n access: 'read',\n register(server, { client, environment }) {\n server.registerTool(\n 'charges_get',\n {\n title: 'Get charge',\n description:\n 'Fetch one charge by id: status, settlement status, amounts, accepted payment pairs, split recipients and timestamps. `metadata` is left out unless includeMetadata is true.',\n inputSchema: GetChargeInputSchema,\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n ({ id, includeMetadata }) =>\n runTool(environment, async () => {\n const charge = await client.charges.get(id)\n const schema = includeMetadata ? ChargeSchema : ChargeWithoutMetadataSchema\n return { charge: schema.parse(charge) }\n }),\n )\n },\n },\n {\n name: 'charges_timeline',\n access: 'read',\n register(server, { client, environment }) {\n server.registerTool(\n 'charges_timeline',\n {\n title: 'Get charge timeline',\n description:\n 'List the lifecycle events of one charge in order (created, transfers detected, confirmed, settled, ...).',\n inputSchema: ChargeIdInputSchema,\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n ({ id }) =>\n runTool(environment, async () => ({\n chargeId: id,\n events: TimelineSchema.parse(await client.charges.getTimeline(id)),\n })),\n )\n },\n },\n {\n name: 'charges_create',\n access: 'write',\n register(server, { client, environment }) {\n server.registerTool(\n 'charges_create',\n {\n title: 'Create charge',\n description: `Create a new ${environment} charge. Escrow charges and redirectUrl are not available through this server. Pass idempotencyKey to make a retry safe; without it every call creates a new charge.`,\n inputSchema: CreateChargeToolInputSchema,\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n (input) =>\n runTool(environment, async () => ({\n charge: ChargeWithoutMetadataSchema.parse(await client.charges.create(input)),\n })),\n )\n },\n },\n {\n name: 'charges_check',\n access: 'write',\n register(server, { client, environment }) {\n server.registerTool(\n 'charges_check',\n {\n title: 'Re-check charge on-chain',\n description:\n \"Ask the API to re-check a charge's payment on-chain now instead of waiting for the background pass. Optionally pass txHash together with network to verify one specific transaction. Never credits anything that is not really on-chain.\",\n inputSchema: CheckChargeInputSchema,\n annotations: {\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n },\n ({ id, ...hint }) =>\n runTool(environment, async () => {\n const request = CheckChargeRequestSchema.safeParse(hint)\n if (!request.success) throw new ToolInputError(validationMessage(request.error))\n const checked = await client.charges.check(id, request.data)\n return { charge: CheckResponseWithoutMetadataSchema.parse(checked) }\n }),\n )\n },\n },\n]\n","import { z } from 'zod'\n\nconst ID_BODY_MAX_LENGTH = 64\n\nfunction prefixedIdSchema(prefix: string, label: string) {\n return z\n .string()\n .regex(\n new RegExp(`^${prefix}_[A-Za-z0-9]{1,${ID_BODY_MAX_LENGTH}}$`),\n `must be a ${label} id like ${prefix}_...`,\n )\n .describe(`A Klappay ${label} id (\\`${prefix}_\\` followed by letters and digits).`)\n}\n\nexport const ChargeIdSchema = prefixedIdSchema('ch', 'charge')\nexport const WebhookIdSchema = prefixedIdSchema('wh', 'webhook')\nexport const WebhookDeliveryIdSchema = prefixedIdSchema('ev', 'webhook delivery')\n","import type { KlapEnvironment } from '@klappay/cli/credentials'\nimport { KlapApiError } from '@klappay/node'\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { ZodError } from 'zod'\n\nexport const UNEXPECTED_ERROR_MESSAGE = 'Unexpected error calling the Klap API'\nexport const UNEXPECTED_RESPONSE_CODE = 'unexpected_response'\nexport const VALIDATION_ERROR_CODE = 'validation_error'\n\nexport type ToolPayload = Record<string, unknown>\n\nexport class ToolInputError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ToolInputError'\n }\n}\n\nfunction toResult(payload: ToolPayload, isError: boolean): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],\n structuredContent: payload,\n ...(isError ? { isError: true } : {}),\n }\n}\n\nexport function successResult(environment: KlapEnvironment, data: ToolPayload): CallToolResult {\n return toResult({ ...data, environment }, false)\n}\n\nfunction describeForLog(err: unknown): string {\n if (!(err instanceof Error)) return typeof err\n const cause =\n err.cause instanceof Error ? ` (cause: ${err.cause.name}: ${err.cause.message})` : ''\n return `${err.name}: ${err.message}${cause}`\n}\n\nfunction zodIssuePaths(err: ZodError): string {\n return err.issues.map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.code}`).join('; ')\n}\n\nexport function errorResult(environment: KlapEnvironment, err: unknown): CallToolResult {\n if (err instanceof KlapApiError) {\n return toResult(\n { environment, error: { code: err.code, status: err.status, message: err.message } },\n true,\n )\n }\n if (err instanceof ToolInputError) {\n return toResult(\n { environment, error: { code: VALIDATION_ERROR_CODE, message: err.message } },\n true,\n )\n }\n if (err instanceof ZodError) {\n console.error(`klap-mcp: API response did not match the expected schema: ${zodIssuePaths(err)}`)\n return toResult(\n {\n environment,\n error: {\n code: UNEXPECTED_RESPONSE_CODE,\n message: 'The Klap API returned a response this server does not recognize.',\n },\n },\n true,\n )\n }\n console.error(`klap-mcp: ${UNEXPECTED_ERROR_MESSAGE}: ${describeForLog(err)}`)\n return toResult(\n { environment, error: { code: 'unexpected_error', message: UNEXPECTED_ERROR_MESSAGE } },\n true,\n )\n}\n\nexport async function runTool(\n environment: KlapEnvironment,\n action: () => Promise<ToolPayload>,\n): Promise<CallToolResult> {\n try {\n return successResult(environment, await action())\n } catch (err) {\n return errorResult(environment, err)\n }\n}\n","import { ChargeSchema } from '@klappay/types'\n\nexport const ChargeWithoutMetadataSchema = ChargeSchema.omit({ metadata: true })\n","import { MetricsQueryResultSchema, MetricsQuerySchema } from '@klappay/types'\nimport { z } from 'zod'\nimport { runTool } from '../result'\nimport type { ToolDefinition } from './definition'\n\nconst MetricsQueryInputSchema = z\n .object({\n query: MetricsQuerySchema.describe(\n 'The metrics query: a resource (charges, transactions or distributions), metrics to aggregate, an ISO 8601 dateRange, optional filters and groupBy.',\n ),\n })\n .strict()\n\nexport const metricsTools: ToolDefinition[] = [\n {\n name: 'metrics_query',\n access: 'read',\n register(server, { client, environment }) {\n server.registerTool(\n 'metrics_query',\n {\n title: 'Query business metrics',\n description:\n \"Run an aggregate query over this environment's charges, transactions or distributions (counts, sums, grouped by date bucket or dimension).\",\n inputSchema: MetricsQueryInputSchema,\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n ({ query }) =>\n runTool(environment, async () =>\n MetricsQueryResultSchema.parse(await client.metrics.query(query)),\n ),\n )\n },\n },\n]\n","import { CapabilitiesSchema } from '@klappay/types'\nimport { runTool } from '../result'\nimport type { ToolDefinition } from './definition'\n\nexport const networkTools: ToolDefinition[] = [\n {\n name: 'networks_get',\n access: 'read',\n register(server, { client, environment }) {\n server.registerTool(\n 'networks_get',\n {\n title: 'Get accepted networks',\n description:\n 'List the (token, network) payment pairs this environment accepts right now — the same list charges_create validates acceptedPayments against.',\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n () =>\n runTool(environment, async () => CapabilitiesSchema.parse(await client.networks.get())),\n )\n },\n },\n]\n","import { SandboxTriggerSchema } from '@klappay/types'\nimport { ChargeIdSchema } from '../ids'\nimport { runTool } from '../result'\nimport { ChargeWithoutMetadataSchema } from './charge-output'\nimport type { ToolDefinition } from './definition'\n\nconst SandboxTriggerInputSchema = SandboxTriggerSchema.extend({ chargeId: ChargeIdSchema }).strict()\n\nexport const sandboxTools: ToolDefinition[] = [\n {\n name: 'sandbox_trigger',\n access: 'sandbox',\n register(server, { client, environment }) {\n server.registerTool(\n 'sandbox_trigger',\n {\n title: 'Simulate sandbox charge event',\n description:\n 'Push a test charge into a payment state (confirmed, partially paid, overpaid, expired, underpaid, settled, settlement failed) without moving funds, and fire the matching webhook. Test environment only.',\n inputSchema: SandboxTriggerInputSchema,\n annotations: {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n ({ chargeId, event, amount }) =>\n runTool(environment, async () => ({\n charge: ChargeWithoutMetadataSchema.parse(\n await client.sandbox.trigger(chargeId, event, amount),\n ),\n })),\n )\n },\n },\n]\n","import { hostOf } from '../redact'\nimport { successResult } from '../result'\nimport type { ToolDefinition } from './definition'\n\nexport const statusTools: ToolDefinition[] = [\n {\n name: 'klap_status',\n access: 'read',\n register(server, { environment, baseUrl }) {\n server.registerTool(\n 'klap_status',\n {\n title: 'Klap connection status',\n description:\n 'Show which Klap environment (test or live) and API host this server is connected to. Makes no API call.',\n annotations: { readOnlyHint: true, openWorldHint: false },\n },\n () => successResult(environment, { host: hostOf(baseUrl) }),\n )\n },\n },\n]\n","import {\n ListWebhookDeliveriesSchema,\n PaginatedWebhookDeliveriesSchema,\n WebhookListItemSchema,\n} from '@klappay/types'\nimport { z } from 'zod'\nimport { WebhookDeliveryIdSchema, WebhookIdSchema } from '../ids'\nimport { redactUrl } from '../redact'\nimport { runTool } from '../result'\nimport type { ToolDefinition } from './definition'\n\nconst WebhookListSchema = z.array(WebhookListItemSchema)\n\nconst ListDeliveriesInputSchema = ListWebhookDeliveriesSchema.extend({\n webhookId: WebhookIdSchema,\n}).strict()\n\nconst RetryDeliveryInputSchema = z\n .object({ webhookId: WebhookIdSchema, deliveryId: WebhookDeliveryIdSchema })\n .strict()\n\nexport const webhookTools: ToolDefinition[] = [\n {\n name: 'webhooks_list',\n access: 'read',\n register(server, { client, environment }) {\n server.registerTool(\n 'webhooks_list',\n {\n title: 'List webhooks',\n description:\n 'List webhook endpoints and the events they subscribe to. Endpoint URLs are shown as origin + path only (credentials and query strings removed); secrets are never returned, only their display hint.',\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n () =>\n runTool(environment, async () => {\n const webhooks = WebhookListSchema.parse(await client.webhooks.list())\n return {\n webhooks: webhooks.map((webhook) => ({ ...webhook, url: redactUrl(webhook.url) })),\n }\n }),\n )\n },\n },\n {\n name: 'webhooks_list_deliveries',\n access: 'read',\n register(server, { client, environment }) {\n server.registerTool(\n 'webhooks_list_deliveries',\n {\n title: 'List webhook deliveries',\n description:\n \"List one webhook's delivery attempts (event, status, attempts, response code), newest first. Paginate with limit and the previous page's nextCursor.\",\n inputSchema: ListDeliveriesInputSchema,\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n ({ webhookId, ...page }) =>\n runTool(environment, async () => {\n const deliveries = await client.webhooks.listDeliveries(webhookId, page)\n return { webhookId, ...PaginatedWebhookDeliveriesSchema.parse(deliveries) }\n }),\n )\n },\n },\n {\n name: 'webhooks_retry_delivery',\n access: 'write',\n register(server, { client, environment }) {\n server.registerTool(\n 'webhooks_retry_delivery',\n {\n title: 'Retry webhook delivery',\n description:\n 'Re-send one past webhook delivery to its endpoint now. The receiving system will process the event again.',\n inputSchema: RetryDeliveryInputSchema,\n annotations: {\n readOnlyHint: false,\n destructiveHint: true,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n ({ webhookId, deliveryId }) =>\n runTool(environment, async () => {\n await client.webhooks.retryDelivery(webhookId, deliveryId)\n return { webhookId, deliveryId, retried: true }\n }),\n )\n },\n },\n]\n"],"mappings":"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","names":["ChargeSchema","z","z","ChargeSchema","z","z","z","z"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
The server reads four environment variables. An empty string counts as
|
|
4
|
+
unset; any other value, including whitespace, is taken literally.
|
|
5
|
+
|
|
6
|
+
| Variable | Values |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `KLAP_ENV` | unset (means `test`), `test` or `live` |
|
|
9
|
+
| `KLAP_API_KEY` | a `klap_test_...` or `klap_live_...` key |
|
|
10
|
+
| `KLAP_BASE_URL` | the Klap API base URL |
|
|
11
|
+
| `KLAP_MCP_ALLOW_LIVE_WRITES` | `1` to enable live writes; anything else leaves them off |
|
|
12
|
+
|
|
13
|
+
## How the key is chosen
|
|
14
|
+
|
|
15
|
+
1. `KLAP_ENV` picks the environment. It must be exactly `test` or `live`
|
|
16
|
+
(`LIVE`, ` live` or `production` all refuse).
|
|
17
|
+
2. If `KLAP_API_KEY` is set, it is the only key source. `~/.klap/config.json`
|
|
18
|
+
is not read at all. `KLAP_BASE_URL` is required, and the key's prefix
|
|
19
|
+
must match the environment: a `klap_test_` key with `KLAP_ENV=live`
|
|
20
|
+
refuses to start, and so does a `klap_live_` key with `KLAP_ENV` unset.
|
|
21
|
+
3. Otherwise the key comes from `~/.klap/config.json`, the slot for the
|
|
22
|
+
chosen environment. When `KLAP_ENV` is unset the test slot is used.
|
|
23
|
+
Unlike the CLI, the server never picks live on its own: if only a live
|
|
24
|
+
key is stored, set `KLAP_ENV=live`. The base URL is the one stored with
|
|
25
|
+
the key. If `KLAP_BASE_URL` is also set it must point to the same place
|
|
26
|
+
(compared on origin and path, ignoring a trailing slash).
|
|
27
|
+
|
|
28
|
+
## Base URL rules
|
|
29
|
+
|
|
30
|
+
Whichever base URL ends up in use must:
|
|
31
|
+
|
|
32
|
+
- parse as a URL;
|
|
33
|
+
- use `https://`, or `http://` only for `localhost`, `127.0.0.1` or `[::1]`;
|
|
34
|
+
- carry no username or password.
|
|
35
|
+
|
|
36
|
+
## Live writes
|
|
37
|
+
|
|
38
|
+
In `live`, only read tools are registered. `KLAP_MCP_ALLOW_LIVE_WRITES=1`
|
|
39
|
+
adds `charges_create`, `charges_check` and `webhooks_retry_delivery`.
|
|
40
|
+
`sandbox_trigger` is never available in live. In `test` the variable has no
|
|
41
|
+
effect; every tool is available.
|
|
42
|
+
|
|
43
|
+
## Refusals
|
|
44
|
+
|
|
45
|
+
When the configuration is unsafe or incomplete the server prints one line
|
|
46
|
+
to stderr and exits with status 1. Messages never contain key material.
|
|
47
|
+
|
|
48
|
+
| Situation | What to do |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `KLAP_ENV` is not `test`/`live` | Fix the value |
|
|
51
|
+
| `KLAP_API_KEY` set, `KLAP_BASE_URL` missing | Set both |
|
|
52
|
+
| `KLAP_API_KEY` prefix unknown | Use a `klap_test_`/`klap_live_` key |
|
|
53
|
+
| Key environment differs from `KLAP_ENV` | Use the matching key or change `KLAP_ENV` |
|
|
54
|
+
| No `~/.klap/config.json` and no `KLAP_API_KEY` | Run `klap login --api-key - --base-url <url>` |
|
|
55
|
+
| Stored config has no key for the environment | `klap login` with that key, or change `KLAP_ENV` |
|
|
56
|
+
| Stored config is corrupted or has a key in the wrong slot | Run `klap logout`, then `klap login` again |
|
|
57
|
+
| `~/.klap` or `~/.klap/config.json` is a symlink | Remove the link, then run `klap login` |
|
|
58
|
+
| `KLAP_BASE_URL` differs from the stored base URL | Unset it, or pass `KLAP_API_KEY` too |
|
|
59
|
+
| Base URL is `http://` on a non-loopback host, or has credentials | Use `https://` without credentials |
|
|
60
|
+
|
|
61
|
+
## Using a key without `klap login`
|
|
62
|
+
|
|
63
|
+
For CI or a throwaway setup you can pass the key directly. It then sits in
|
|
64
|
+
your MCP client's config file, so prefer `klap login` on a workstation:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"klap-local": {
|
|
70
|
+
"command": "npx",
|
|
71
|
+
"args": ["-y", "@klappay/mcp@0.1.0"],
|
|
72
|
+
"env": {
|
|
73
|
+
"KLAP_API_KEY": "klap_test_...",
|
|
74
|
+
"KLAP_BASE_URL": "http://localhost:3000"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
## 1. Store a test key
|
|
4
|
+
|
|
5
|
+
`@klappay/mcp` reads the same `~/.klap/config.json` the Klap CLI writes, so
|
|
6
|
+
the key never has to appear in your MCP client's config file:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npx @klappay/cli login --api-key - --base-url https://api.klap.example
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Paste a `klap_test_...` key on stdin and press Ctrl-D. Reading it from stdin
|
|
13
|
+
(`--api-key -`) keeps it out of your shell history; setting `KLAP_API_KEY`
|
|
14
|
+
before running the command works too. `--base-url` is required. The file is created with `0600` permissions.
|
|
15
|
+
You can store a live key the same way later; both slots live side by side.
|
|
16
|
+
|
|
17
|
+
## 2. Add the server
|
|
18
|
+
|
|
19
|
+
Claude Code:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
claude mcp add klap-test -- npx -y @klappay/mcp@0.1.0
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Claude Desktop (`claude_desktop_config.json`) or Cursor (`~/.cursor/mcp.json`):
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"klap-test": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "@klappay/mcp@0.1.0"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Restart the client. Its MCP log should show
|
|
39
|
+
`klap-mcp: test → <your API host>`. If the server refuses to start, the
|
|
40
|
+
same log has a one-line reason; see
|
|
41
|
+
[Configuration](./configuration.md#refusals).
|
|
42
|
+
|
|
43
|
+
## 3. Try it
|
|
44
|
+
|
|
45
|
+
Ask the assistant things like:
|
|
46
|
+
|
|
47
|
+
- "Which Klap environment are you connected to?" (`klap_status`)
|
|
48
|
+
- "Create a 25 USD test charge payable in USDC on Base, expiring in 15
|
|
49
|
+
minutes." (`charges_create`)
|
|
50
|
+
- "Mark that charge as confirmed in the sandbox, then show me its
|
|
51
|
+
timeline." (`sandbox_trigger`, `charges_timeline`)
|
|
52
|
+
- "Which of my webhooks failed deliveries recently?" (`webhooks_list`,
|
|
53
|
+
`webhooks_list_deliveries`)
|
|
54
|
+
|
|
55
|
+
## 4. Add live (optional)
|
|
56
|
+
|
|
57
|
+
Add a second, separate server with `KLAP_ENV=live`. It is read-only:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
claude mcp add klap-live -e KLAP_ENV=live -- npx -y @klappay/mcp@0.1.0
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Only add `KLAP_MCP_ALLOW_LIVE_WRITES=1` if you really want the assistant to
|
|
64
|
+
create live charges or re-send live webhooks; read
|
|
65
|
+
[Security](./security.md) first.
|
|
66
|
+
|
|
67
|
+
## Where to go next
|
|
68
|
+
|
|
69
|
+
- [Configuration](./configuration.md): every variable and refusal
|
|
70
|
+
- [Tools](./tools.md): what each tool takes and returns
|
|
71
|
+
- [Security](./security.md): what is excluded and why
|
|
72
|
+
|
|
73
|
+
## For LLMs and agents
|
|
74
|
+
|
|
75
|
+
This site publishes [`llms.txt`](https://mcp.klappay.com/llms.txt), a link
|
|
76
|
+
index of every doc page, and
|
|
77
|
+
[`llms-full.txt`](https://mcp.klappay.com/llms-full.txt), the full content
|
|
78
|
+
of every doc page in one plain-text file. Point an agent or RAG pipeline at
|
|
79
|
+
either to give it these docs without scraping HTML. Both regenerate on
|
|
80
|
+
every deploy, so they never drift from the pages here.
|
|
Binary file
|
|
Binary file
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
An MCP server hands an AI model the ability to call an API with your key.
|
|
4
|
+
The model can be wrong, and text it reads (a charge's metadata, a webhook
|
|
5
|
+
URL, an API error) can try to steer it. This server is built so that a
|
|
6
|
+
confused or manipulated assistant can do as little damage as possible.
|
|
7
|
+
|
|
8
|
+
## Where the key goes
|
|
9
|
+
|
|
10
|
+
- The key is read from `~/.klap/config.json` (via
|
|
11
|
+
`@klappay/cli/credentials`, which refuses symlinked paths and keys stored
|
|
12
|
+
in the wrong slot) or from `KLAP_API_KEY`. It is sent only as the
|
|
13
|
+
`Authorization` header to the configured base URL.
|
|
14
|
+
- A stored key is bound to the base URL stored with it. Setting a different
|
|
15
|
+
`KLAP_BASE_URL` refuses to start rather than sending that key to a new
|
|
16
|
+
host. To use another host, pass `KLAP_API_KEY` and `KLAP_BASE_URL`
|
|
17
|
+
together.
|
|
18
|
+
- `http://` is only accepted for loopback hosts; base URLs with embedded
|
|
19
|
+
credentials are refused.
|
|
20
|
+
- The key prefix must match `KLAP_ENV`, so a live key can't be used by a
|
|
21
|
+
server you think is in test, or the other way round.
|
|
22
|
+
- The key never appears in tool results, startup output or refusal
|
|
23
|
+
messages. Request logging (`debug`) is never enabled.
|
|
24
|
+
|
|
25
|
+
## Live is read-only by default
|
|
26
|
+
|
|
27
|
+
In `live` only read tools are registered. The model can't call a write tool
|
|
28
|
+
that doesn't exist, whatever it is told. `KLAP_MCP_ALLOW_LIVE_WRITES=1`
|
|
29
|
+
(exactly `1`) adds `charges_create`, `charges_check` and
|
|
30
|
+
`webhooks_retry_delivery`. `sandbox_trigger` is never available in live.
|
|
31
|
+
Run test and live as separate servers so you can enable or disable live
|
|
32
|
+
independently.
|
|
33
|
+
|
|
34
|
+
## Excluded on purpose
|
|
35
|
+
|
|
36
|
+
| Not exposed | Why |
|
|
37
|
+
|---|---|
|
|
38
|
+
| Escrow `release` / `refund` | They move funds out of a charge. That stays a deliberate human action. |
|
|
39
|
+
| Webhook create / delete / rotate secret | Pointing webhooks somewhere else, or rotating a secret, can silently break or redirect your integration. |
|
|
40
|
+
| Recipient (payout) changes | They decide where money goes. |
|
|
41
|
+
| Live event streams (`watch`) | Long-lived streams don't fit a request/response tool. |
|
|
42
|
+
| QR codes and swap quotes | Payer-facing features with no use to an assistant. |
|
|
43
|
+
|
|
44
|
+
## What the model sees
|
|
45
|
+
|
|
46
|
+
- Responses are parsed through `@klappay/types` schemas, dropping any field
|
|
47
|
+
the schema doesn't define. A response that doesn't fit the schema is
|
|
48
|
+
reported as `unexpected_response`, not forwarded.
|
|
49
|
+
- A charge's `metadata` (free-form merchant data, possibly customer
|
|
50
|
+
details) is only included when `charges_get` is called with
|
|
51
|
+
`includeMetadata: true`.
|
|
52
|
+
- Webhook URLs are reduced to origin + path, removing credentials and
|
|
53
|
+
tokens people often put in a URL's userinfo or query string.
|
|
54
|
+
- Unexpected errors return a generic message; details go to stderr for you,
|
|
55
|
+
not to the model.
|
|
56
|
+
|
|
57
|
+
## stdout
|
|
58
|
+
|
|
59
|
+
stdout carries only MCP JSON-RPC messages. `console.log`, `console.info`
|
|
60
|
+
and `console.debug` are redirected to stderr before anything else loads,
|
|
61
|
+
so no dependency can corrupt the protocol stream.
|
|
62
|
+
|
|
63
|
+
## Reporting a problem
|
|
64
|
+
|
|
65
|
+
Report security issues privately to the Klappay team rather than in a
|
|
66
|
+
public issue.
|
package/docs/tools.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Tools
|
|
2
|
+
|
|
3
|
+
Every result is a JSON object with an `environment` field (`test` or
|
|
4
|
+
`live`). It is returned both as text and as `structuredContent`. API
|
|
5
|
+
responses are parsed through the matching `@klappay/types` schema first:
|
|
6
|
+
fields the schema doesn't know are dropped, and a response that doesn't
|
|
7
|
+
match at all becomes an `unexpected_response` error instead of being passed
|
|
8
|
+
through.
|
|
9
|
+
|
|
10
|
+
Ids are checked before any request is sent: charge ids look like `ch_...`,
|
|
11
|
+
webhook ids `wh_...`, delivery ids `ev_...`, followed by letters and digits
|
|
12
|
+
only.
|
|
13
|
+
|
|
14
|
+
## Errors
|
|
15
|
+
|
|
16
|
+
A failed call returns `isError: true` with:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"environment": "test",
|
|
21
|
+
"error": { "code": "charge_not_found", "status": 404, "message": "Charge not found" }
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`code` and `status` come straight from the Klap API. Locally detected
|
|
26
|
+
problems use `validation_error` (invalid input), `unexpected_response` (the
|
|
27
|
+
API answered with something the schema rejects) or `unexpected_error`
|
|
28
|
+
(anything else; details go to the server's stderr only). Input that fails
|
|
29
|
+
the tool's schema is rejected by the MCP SDK before the tool runs.
|
|
30
|
+
|
|
31
|
+
## Read tools
|
|
32
|
+
|
|
33
|
+
Available in every environment, annotated `readOnlyHint: true`.
|
|
34
|
+
|
|
35
|
+
### `klap_status`
|
|
36
|
+
|
|
37
|
+
No input. Returns `{ environment, host }`, where `host` is the API host
|
|
38
|
+
(and port) only. Makes no API call.
|
|
39
|
+
|
|
40
|
+
### `charges_get`
|
|
41
|
+
|
|
42
|
+
| Input | |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `id` | charge id |
|
|
45
|
+
| `includeMetadata` | boolean, default `false` |
|
|
46
|
+
|
|
47
|
+
Returns `{ charge }` (`ChargeSchema`). The charge's `metadata` object
|
|
48
|
+
(free-form merchant data, which may contain customer details) is left out
|
|
49
|
+
unless `includeMetadata` is `true`.
|
|
50
|
+
|
|
51
|
+
### `charges_timeline`
|
|
52
|
+
|
|
53
|
+
Input `{ id }`. Returns `{ chargeId, events }` (`TimelineEventSchema[]`).
|
|
54
|
+
|
|
55
|
+
### `webhooks_list`
|
|
56
|
+
|
|
57
|
+
No input. Returns `{ webhooks }` (`WebhookListItemSchema[]`). Each `url` is
|
|
58
|
+
reduced to origin + path: any username/password, query string and fragment
|
|
59
|
+
are removed. Secrets are never returned, only the display `hint`.
|
|
60
|
+
|
|
61
|
+
### `webhooks_list_deliveries`
|
|
62
|
+
|
|
63
|
+
| Input | |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `webhookId` | webhook id |
|
|
66
|
+
| `limit` | 1–100, default 20 |
|
|
67
|
+
| `cursor` | the previous page's `nextCursor` |
|
|
68
|
+
|
|
69
|
+
Returns `{ webhookId, data, nextCursor, hasMore }`
|
|
70
|
+
(`PaginatedWebhookDeliveriesSchema`).
|
|
71
|
+
|
|
72
|
+
### `networks_get`
|
|
73
|
+
|
|
74
|
+
No input. Returns `{ acceptedPayments }` (`CapabilitiesSchema`): the
|
|
75
|
+
`(token, network)` pairs this environment accepts for new charges.
|
|
76
|
+
|
|
77
|
+
### `metrics_query`
|
|
78
|
+
|
|
79
|
+
Input `{ query }`, where `query` is a `MetricsQuerySchema` request
|
|
80
|
+
(resource, metrics, `dateRange`, optional filters/groupBy/limit). Returns
|
|
81
|
+
the `MetricsQueryResultSchema` fields (`data` rows and `meta`).
|
|
82
|
+
|
|
83
|
+
## Write tools
|
|
84
|
+
|
|
85
|
+
Available in `test`, and in `live` only with `KLAP_MCP_ALLOW_LIVE_WRITES=1`.
|
|
86
|
+
Annotated `readOnlyHint: false`.
|
|
87
|
+
|
|
88
|
+
### `charges_create`
|
|
89
|
+
|
|
90
|
+
Input is `CreateChargeSchema` without `escrow` and `redirectUrl`; sending
|
|
91
|
+
either is rejected. Pass `idempotencyKey` to make a retry safe; without it
|
|
92
|
+
every call creates a new charge. Returns `{ charge }` without `metadata`
|
|
93
|
+
(including `amount`, `acceptedPayments` and `splitRecipients`).
|
|
94
|
+
|
|
95
|
+
Annotations: `destructiveHint: false`, `idempotentHint: false`.
|
|
96
|
+
|
|
97
|
+
### `charges_check`
|
|
98
|
+
|
|
99
|
+
| Input | |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `id` | charge id |
|
|
102
|
+
| `txHash` | optional `0x` + 64 hex transaction hash |
|
|
103
|
+
| `network` | required together with `txHash` |
|
|
104
|
+
|
|
105
|
+
Asks the API to re-check the charge on-chain now. Returns `{ charge }`
|
|
106
|
+
(`CheckChargeResponseSchema` without `metadata`).
|
|
107
|
+
|
|
108
|
+
Annotations: `destructiveHint: false`, `idempotentHint: true`.
|
|
109
|
+
|
|
110
|
+
### `webhooks_retry_delivery`
|
|
111
|
+
|
|
112
|
+
Input `{ webhookId, deliveryId }`. Re-sends one delivery to its endpoint;
|
|
113
|
+
the receiver processes that event again. Returns
|
|
114
|
+
`{ webhookId, deliveryId, retried: true }`.
|
|
115
|
+
|
|
116
|
+
Annotations: `destructiveHint: true`.
|
|
117
|
+
|
|
118
|
+
## Sandbox tool
|
|
119
|
+
|
|
120
|
+
### `sandbox_trigger`
|
|
121
|
+
|
|
122
|
+
Test environment only, never registered in live.
|
|
123
|
+
|
|
124
|
+
| Input | |
|
|
125
|
+
|---|---|
|
|
126
|
+
| `chargeId` | charge id |
|
|
127
|
+
| `event` | `charge.confirmed`, `charge.partially_paid`, `charge.overpaid`, `charge.expired`, `charge.underpaid`, `charge.settled` or `charge.settlement_failed` |
|
|
128
|
+
| `amount` | optional, for `charge.partially_paid`/`charge.overpaid` |
|
|
129
|
+
|
|
130
|
+
Returns `{ charge }` without `metadata`. Annotations: `destructiveHint: true`
|
|
131
|
+
(the state change can't be undone on that charge).
|
package/package.json
CHANGED
|
@@ -1,6 +1,71 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klappay/mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Local stdio MCP server for the Klap Core API — lets an AI assistant read charges, webhooks, networks and metrics, and (in test mode) create charges and simulate sandbox events, using the key `klap login` already stored on your machine.",
|
|
6
|
+
"keywords": ["klap", "klappay", "mcp", "model-context-protocol", "crypto", "payments", "web3"],
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/klappay/klap-mcp.git"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=24"
|
|
13
|
+
},
|
|
14
|
+
"packageManager": "pnpm@10.33.0",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"klap-mcp": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"README.md",
|
|
23
|
+
"docs",
|
|
24
|
+
"!docs/.vitepress",
|
|
25
|
+
"!docs/index.md",
|
|
26
|
+
"!docs/public/CNAME"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"lint": "biome check .",
|
|
36
|
+
"lint:fix": "biome check --write .",
|
|
37
|
+
"format": "biome format --write .",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"docs:dev": "vitepress dev docs",
|
|
40
|
+
"docs:build": "vitepress build docs",
|
|
41
|
+
"docs:preview": "vitepress preview docs",
|
|
42
|
+
"changeset": "changeset",
|
|
43
|
+
"version": "changeset version",
|
|
44
|
+
"release": "pnpm build && changeset publish",
|
|
45
|
+
"prepare": "husky"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@klappay/cli": "^1.4.0",
|
|
49
|
+
"@klappay/node": "^5.1.4",
|
|
50
|
+
"@klappay/types": "6.0.0",
|
|
51
|
+
"@modelcontextprotocol/sdk": "1.32.1",
|
|
52
|
+
"zod": "3.25.76"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@biomejs/biome": "^1.9.0",
|
|
56
|
+
"@changesets/cli": "^2.31.1",
|
|
57
|
+
"@commitlint/cli": "^19.5.0",
|
|
58
|
+
"@commitlint/config-conventional": "^19.5.0",
|
|
59
|
+
"@types/node": "^20.16.0",
|
|
60
|
+
"husky": "^9.1.0",
|
|
61
|
+
"lint-staged": "^15.2.0",
|
|
62
|
+
"tsup": "^8.3.0",
|
|
63
|
+
"typescript": "^5.6.0",
|
|
64
|
+
"vitepress": "^1.6.4",
|
|
65
|
+
"vitepress-plugin-llms": "^1.14.0",
|
|
66
|
+
"vitest": "^3.2.6"
|
|
67
|
+
},
|
|
68
|
+
"pnpm": {
|
|
69
|
+
"onlyBuiltDependencies": ["@biomejs/biome", "esbuild"]
|
|
70
|
+
}
|
|
71
|
+
}
|