Skip to content

Commit e904a70

Browse files
authored
Merge pull request #13 from moneydevkit/mdk-456
Mdk 456: Auth'd MCP Tools
2 parents 967248c + f479993 commit e904a70

9 files changed

Lines changed: 432 additions & 1 deletion

File tree

src/contracts/checkout.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import { oc } from "@orpc/contract";
22
import { z } from "zod";
33
import { CheckoutSchema } from "../schemas/checkout";
44
import { CurrencySchema } from "../schemas/currency";
5+
import { CustomerSchema } from "../schemas/customer";
6+
import {
7+
PaginationInputSchema,
8+
PaginationOutputSchema,
9+
} from "../schemas/pagination";
510

611
/**
712
* Helper to treat empty strings as undefined (not provided).
@@ -142,10 +147,74 @@ export const paymentReceivedContract = oc
142147
.input(PaymentReceivedInputSchema)
143148
.output(z.object({ ok: z.boolean() }));
144149

150+
// List checkouts schemas
151+
const CheckoutStatusSchema = z.enum([
152+
"UNCONFIRMED",
153+
"CONFIRMED",
154+
"PENDING_PAYMENT",
155+
"PAYMENT_RECEIVED",
156+
"EXPIRED",
157+
]);
158+
159+
const ListCheckoutsInputSchema = PaginationInputSchema.extend({
160+
status: CheckoutStatusSchema.optional(),
161+
});
162+
163+
const ListCheckoutsOutputSchema = PaginationOutputSchema.extend({
164+
checkouts: z.array(CheckoutSchema),
165+
});
166+
167+
export const listCheckoutsContract = oc
168+
.input(ListCheckoutsInputSchema)
169+
.output(ListCheckoutsOutputSchema);
170+
171+
// MCP-specific embedded customer schema
172+
const CheckoutCustomerSchema = CustomerSchema.nullable();
173+
174+
// MCP-specific summary schema for list (simpler than full CheckoutSchema)
175+
const CheckoutListItemSchema = z.object({
176+
id: z.string(),
177+
status: CheckoutStatusSchema,
178+
type: z.enum(["PRODUCTS", "AMOUNT", "TOP_UP"]),
179+
currency: CurrencySchema,
180+
totalAmount: z.number().nullable(),
181+
customerId: z.string().nullable(),
182+
customer: CheckoutCustomerSchema,
183+
productId: z.string().nullable(),
184+
organizationId: z.string(),
185+
expiresAt: z.date(),
186+
createdAt: z.date(),
187+
modifiedAt: z.date().nullable(),
188+
});
189+
190+
// MCP-specific detailed schema for get (includes additional fields)
191+
const CheckoutDetailSchema = CheckoutListItemSchema.extend({
192+
userMetadata: z.record(z.unknown()).nullable(),
193+
successUrl: z.string().nullable(),
194+
discountAmount: z.number().nullable(),
195+
netAmount: z.number().nullable(),
196+
taxAmount: z.number().nullable(),
197+
});
198+
199+
const ListCheckoutsSummaryOutputSchema = PaginationOutputSchema.extend({
200+
checkouts: z.array(CheckoutListItemSchema),
201+
});
202+
203+
export const listCheckoutsSummaryContract = oc
204+
.input(ListCheckoutsInputSchema)
205+
.output(ListCheckoutsSummaryOutputSchema);
206+
207+
export const getCheckoutSummaryContract = oc
208+
.input(GetCheckoutInputSchema)
209+
.output(CheckoutDetailSchema);
210+
145211
export const checkout = {
146212
get: getCheckoutContract,
147213
create: createCheckoutContract,
148214
confirm: confirmCheckoutContract,
149215
registerInvoice: registerInvoiceContract,
150216
paymentReceived: paymentReceivedContract,
217+
list: listCheckoutsContract,
218+
listSummary: listCheckoutsSummaryContract,
219+
getSummary: getCheckoutSummaryContract,
151220
};

src/contracts/customer.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { oc } from "@orpc/contract";
2+
import { z } from "zod";
3+
import { CustomerSchema } from "../schemas/customer";
4+
import {
5+
PaginationInputSchema,
6+
PaginationOutputSchema,
7+
} from "../schemas/pagination";
8+
9+
const ListCustomersInputSchema = PaginationInputSchema;
10+
const ListCustomersOutputSchema = PaginationOutputSchema.extend({
11+
customers: z.array(CustomerSchema),
12+
});
13+
14+
const GetCustomerInputSchema = z.object({ id: z.string() });
15+
16+
const CreateCustomerInputSchema = z.object({
17+
name: z.string().min(1),
18+
email: z.string().email(),
19+
});
20+
21+
const UpdateCustomerInputSchema = z.object({
22+
id: z.string(),
23+
name: z.string().optional(),
24+
email: z.string().email().optional(),
25+
userMetadata: z.record(z.string(), z.string()).optional(),
26+
});
27+
28+
const DeleteCustomerInputSchema = z.object({ id: z.string() });
29+
30+
export const listCustomersContract = oc
31+
.input(ListCustomersInputSchema)
32+
.output(ListCustomersOutputSchema);
33+
34+
export const getCustomerContract = oc
35+
.input(GetCustomerInputSchema)
36+
.output(CustomerSchema);
37+
38+
export const createCustomerContract = oc
39+
.input(CreateCustomerInputSchema)
40+
.output(CustomerSchema);
41+
42+
export const updateCustomerContract = oc
43+
.input(UpdateCustomerInputSchema)
44+
.output(CustomerSchema);
45+
46+
export const deleteCustomerContract = oc
47+
.input(DeleteCustomerInputSchema)
48+
.output(z.object({ ok: z.literal(true) }));
49+
50+
export const customer = {
51+
list: listCustomersContract,
52+
get: getCustomerContract,
53+
create: createCustomerContract,
54+
update: updateCustomerContract,
55+
delete: deleteCustomerContract,
56+
};

src/contracts/order.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { oc } from "@orpc/contract";
2+
import { z } from "zod";
3+
import { CustomerSchema } from "../schemas/customer";
4+
import { OrderItemSchema, OrderSchema } from "../schemas/order";
5+
import {
6+
PaginationInputSchema,
7+
PaginationOutputSchema,
8+
} from "../schemas/pagination";
9+
10+
// Order with related data for list and get views
11+
const OrderWithRelationsSchema = OrderSchema.extend({
12+
customer: CustomerSchema.nullable(),
13+
orderItems: z.array(OrderItemSchema),
14+
});
15+
16+
const ListOrdersInputSchema = PaginationInputSchema.extend({
17+
customerId: z.string().optional(),
18+
status: z.string().optional(), // Prisma uses String type for status
19+
});
20+
21+
const ListOrdersOutputSchema = PaginationOutputSchema.extend({
22+
orders: z.array(OrderWithRelationsSchema),
23+
});
24+
25+
export const listOrdersContract = oc
26+
.input(ListOrdersInputSchema)
27+
.output(ListOrdersOutputSchema);
28+
29+
export const getOrderContract = oc
30+
.input(z.object({ id: z.string() }))
31+
.output(OrderWithRelationsSchema);
32+
33+
export const order = {
34+
list: listOrdersContract,
35+
get: getOrderContract,
36+
};

src/contracts/products.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { oc } from "@orpc/contract";
22
import { z } from "zod";
33
import { CurrencySchema } from "../schemas/currency";
4+
import { ProductPriceInputSchema } from "../schemas/product-price-input";
45

56
export const ProductPriceSchema = z.object({
67
id: z.string(),
@@ -31,6 +32,42 @@ export const listProductsContract = oc
3132
.input(z.object({}).optional())
3233
.output(ListProductsOutputSchema);
3334

35+
// CRUD input schemas
36+
const CreateProductInputSchema = z.object({
37+
name: z.string().min(1),
38+
description: z.string().optional(),
39+
price: ProductPriceInputSchema,
40+
userMetadata: z.record(z.string(), z.string()).optional(),
41+
});
42+
43+
const UpdateProductInputSchema = z.object({
44+
id: z.string(),
45+
name: z.string().min(1).optional(),
46+
description: z.string().optional(),
47+
price: ProductPriceInputSchema.optional(),
48+
userMetadata: z.record(z.string(), z.string()).optional(),
49+
});
50+
51+
export const getProductContract = oc
52+
.input(z.object({ id: z.string() }))
53+
.output(ProductSchema);
54+
55+
export const createProductContract = oc
56+
.input(CreateProductInputSchema)
57+
.output(ProductSchema);
58+
59+
export const updateProductContract = oc
60+
.input(UpdateProductInputSchema)
61+
.output(ProductSchema);
62+
63+
export const deleteProductContract = oc
64+
.input(z.object({ id: z.string() }))
65+
.output(z.object({ ok: z.literal(true) }));
66+
3467
export const products = {
3568
list: listProductsContract,
69+
get: getProductContract,
70+
create: createProductContract,
71+
update: updateProductContract,
72+
delete: deleteProductContract,
3673
};

src/index.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { checkout } from "./contracts/checkout";
2+
import { customer } from "./contracts/customer";
23
import { onboarding } from "./contracts/onboarding";
4+
import { order } from "./contracts/order";
35
import { products } from "./contracts/products";
46

57
export type {
@@ -28,7 +30,57 @@ export {
2830
ListProductsOutputSchema,
2931
} from "./contracts/products";
3032

31-
export const contract = { checkout, onboarding, products };
33+
// New MCP schemas
34+
export type { Customer } from "./schemas/customer";
35+
export { CustomerSchema } from "./schemas/customer";
36+
export type { Order, OrderItem, OrderStatus } from "./schemas/order";
37+
export {
38+
OrderSchema,
39+
OrderItemSchema,
40+
OrderStatusSchema,
41+
} from "./schemas/order";
42+
export type { PaginationInput, PaginationOutput } from "./schemas/pagination";
43+
export {
44+
PaginationInputSchema,
45+
PaginationOutputSchema,
46+
} from "./schemas/pagination";
47+
export type {
48+
ProductPriceInput,
49+
RecurringIntervalInput,
50+
} from "./schemas/product-price-input";
51+
export {
52+
ProductPriceInputSchema,
53+
RecurringIntervalInputSchema,
54+
} from "./schemas/product-price-input";
55+
56+
// Unified contract - contains all methods from both SDK and MCP
57+
export const contract = { checkout, customer, onboarding, order, products };
58+
59+
// SDK contract - only the methods the SDK router implements
60+
export const sdkContract = {
61+
checkout: {
62+
get: checkout.get,
63+
create: checkout.create,
64+
confirm: checkout.confirm,
65+
registerInvoice: checkout.registerInvoice,
66+
paymentReceived: checkout.paymentReceived,
67+
},
68+
onboarding,
69+
products: {
70+
list: products.list,
71+
},
72+
};
73+
74+
// MCP contract - only the methods the MCP router implements
75+
export const mcpContract = {
76+
customer,
77+
order,
78+
checkout: {
79+
list: checkout.listSummary,
80+
get: checkout.getSummary,
81+
},
82+
products,
83+
};
3284

3385
export type { MetadataValidationError } from "./validation/metadata-validation";
3486
export {

src/schemas/customer.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { z } from "zod";
2+
3+
/**
4+
* Customer schema for MCP API responses.
5+
* Represents a customer in the organization.
6+
* Note: Uses modifiedAt to match Prisma schema naming.
7+
*/
8+
export const CustomerSchema = z.object({
9+
id: z.string(),
10+
name: z.string().nullable(),
11+
email: z.string().nullable(),
12+
emailVerified: z.boolean(),
13+
externalId: z.string().nullable(),
14+
userMetadata: z.record(z.string(), z.any()).nullable(),
15+
organizationId: z.string(),
16+
createdAt: z.date(),
17+
modifiedAt: z.date().nullable(),
18+
});
19+
20+
export type Customer = z.infer<typeof CustomerSchema>;

src/schemas/order.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { z } from "zod";
2+
import { CurrencySchema } from "./currency";
3+
4+
/**
5+
* Order status enum matching Prisma OrderStatus.
6+
* Note: Prisma uses String type, so we validate against known values.
7+
*/
8+
export const OrderStatusSchema = z.enum([
9+
"PENDING",
10+
"PAID",
11+
"REFUNDED",
12+
"CANCELLED",
13+
]);
14+
15+
export type OrderStatus = z.infer<typeof OrderStatusSchema>;
16+
17+
/**
18+
* Order item schema representing a line item in an order.
19+
* Note: Uses modifiedAt to match Prisma schema naming.
20+
*/
21+
export const OrderItemSchema = z.object({
22+
id: z.string(),
23+
orderId: z.string(),
24+
productPriceId: z.string().nullable(),
25+
label: z.string(),
26+
amount: z.number(),
27+
createdAt: z.date(),
28+
modifiedAt: z.date().nullable(),
29+
});
30+
31+
export type OrderItem = z.infer<typeof OrderItemSchema>;
32+
33+
/**
34+
* Order schema for MCP API responses.
35+
* Note: Uses modifiedAt to match Prisma schema naming.
36+
* Note: Order doesn't have totalAmount directly - it's calculated from subtotalAmount + taxAmount.
37+
*/
38+
export const OrderSchema = z.object({
39+
id: z.string(),
40+
organizationId: z.string(),
41+
customerId: z.string().nullable(),
42+
status: OrderStatusSchema,
43+
currency: CurrencySchema,
44+
subtotalAmount: z.number(),
45+
taxAmount: z.number(),
46+
userMetadata: z.record(z.string(), z.any()).nullable(),
47+
createdAt: z.date(),
48+
modifiedAt: z.date().nullable(),
49+
});
50+
51+
export type Order = z.infer<typeof OrderSchema>;

src/schemas/pagination.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { z } from "zod";
2+
3+
/**
4+
* Pagination input schema for list operations.
5+
* Uses cursor-based pagination for efficient large dataset traversal.
6+
*/
7+
export const PaginationInputSchema = z.object({
8+
limit: z.number().int().min(1).max(100).default(50),
9+
cursor: z.string().optional(),
10+
});
11+
12+
export type PaginationInput = z.infer<typeof PaginationInputSchema>;
13+
14+
/**
15+
* Pagination output schema for list operations.
16+
* Returns a cursor for the next page, or null if no more results.
17+
*/
18+
export const PaginationOutputSchema = z.object({
19+
nextCursor: z.string().nullable(),
20+
});
21+
22+
export type PaginationOutput = z.infer<typeof PaginationOutputSchema>;

0 commit comments

Comments
 (0)