Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/mcp-client-inject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Added `wrapClient` to `mppx/mcp/client` for adding automatic payment handling to an SDK-owned MCP client in place: the client is mutated and the same reference is returned, the MCP SDK `callTool(params, resultSchema?, options?)` signature is preserved (method `context` is passed via the options argument), and payment challenges returned as tool results via `org.paymentauth/payment-required` metadata are handled alongside payment-required errors.
5 changes: 5 additions & 0 deletions .changeset/mcp-entrypoints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mppx': patch
---

Moved the MCP entrypoints to `mppx/mcp/client` and `mppx/mcp/server`; the `mppx/mcp-sdk/client` and `mppx/mcp-sdk/server` specifiers remain as aliases.
22 changes: 16 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,25 @@
"types": "./dist/evm/server/index.d.ts",
"default": "./dist/evm/server/index.js"
},
"./mcp/client": {
"src": "./src/mcp/client/index.ts",
"types": "./dist/mcp/client/index.d.ts",
"default": "./dist/mcp/client/index.js"
},
"./mcp/server": {
"src": "./src/mcp/server/index.ts",
"types": "./dist/mcp/server/index.d.ts",
"default": "./dist/mcp/server/index.js"
},
"./mcp-sdk/client": {
"src": "./src/mcp-sdk/client/index.ts",
"types": "./dist/mcp-sdk/client/index.d.ts",
"default": "./dist/mcp-sdk/client/index.js"
"src": "./src/mcp/client/index.ts",
"types": "./dist/mcp/client/index.d.ts",
"default": "./dist/mcp/client/index.js"
},
"./mcp-sdk/server": {
"src": "./src/mcp-sdk/server/index.ts",
"types": "./dist/mcp-sdk/server/index.d.ts",
"default": "./dist/mcp-sdk/server/index.js"
"src": "./src/mcp/server/index.ts",
"types": "./dist/mcp/server/index.d.ts",
"default": "./dist/mcp/server/index.js"
},
"./proxy": {
"src": "./src/proxy/index.ts",
Expand Down
4 changes: 4 additions & 0 deletions src/Mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export const paymentVerificationFailedCode = -32043
/** MCP metadata key for credentials. */
export const credentialMetaKey = 'org.paymentauth/credential'

/** MCP metadata key for payment-required tool results. */
export const paymentRequiredMetaKey = 'org.paymentauth/payment-required'

/** MCP metadata key for receipts. */
export const receiptMetaKey = 'org.paymentauth/receipt'

Expand Down Expand Up @@ -45,6 +48,7 @@ export type JsonRpcRequest = Request & {
*/
export type Result = {
_meta?: {
[paymentRequiredMetaKey]?: ErrorObject['data']
[receiptMetaKey]?: Receipt
[key: string]: unknown
}
Expand Down
228 changes: 0 additions & 228 deletions src/mcp-sdk/client/McpClient.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,76 @@ describe('McpClient.wrap.CallToolOptions', () => {
expectTypeOf<Options>().toHaveProperty('timeout')
})
})

describe('McpClient.wrapClient', () => {
test('returns the original client with payment-aware callTool', () => {
const client = {} as Client
const wrapped = McpClient.wrapClient(client, {
methods: [
tempo({
account: {} as Account,
}),
],
})

expectTypeOf(wrapped).toEqualTypeOf<
McpClient.wrapClient.McpClient<Client, readonly [ReturnType<typeof tempo>]>
>()
expectTypeOf(wrapped.callTool).toBeFunction()
expectTypeOf(wrapped.callTool).returns.toExtend<Promise<McpClient.CallToolResult>>()
})

test('callTool keeps the MCP SDK result schema and options positions', () => {
const client = {} as Client
const wrapped = McpClient.wrapClient(client, {
methods: [
tempo({
account: {} as Account,
}),
],
})

expectTypeOf(wrapped.callTool).toBeCallableWith({ name: 'tool' }, undefined, {
timeout: 5000,
})
})

test('callTool accepts context in the options position', () => {
const client = {} as Client
const wrapped = McpClient.wrapClient(client, {
methods: [tempo({})],
})

expectTypeOf(wrapped.callTool).toBeCallableWith({ name: 'tool' }, undefined, {
context: { account: {} as Account },
timeout: 5000,
})
})

test('can store an inferred client as the exported client type', () => {
const client = {} as Client

const wrapped = McpClient.wrapClient(client, {
methods: [tempo({ account: {} as Account })],
})

expectTypeOf(wrapped).toMatchTypeOf<McpClient.wrapClient.McpClient>()
})
})

describe('McpClient.wrapClient.McpClient', () => {
test('has callTool with correct signature', () => {
type WrappedClient = McpClient.wrapClient.McpClient

expectTypeOf<WrappedClient>().toHaveProperty('callTool')
})
})

describe('McpClient.wrapClient.CallToolOptions', () => {
test('has context and timeout properties', () => {
type Options = McpClient.wrapClient.CallToolOptions

expectTypeOf<Options>().toHaveProperty('context')
expectTypeOf<Options>().toHaveProperty('timeout')
})
})
Loading
Loading