@clawpify/skills 1.0.1
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 +73 -0
- package/clawpify/SKILL.md +134 -0
- package/clawpify/references/blogs.md +385 -0
- package/clawpify/references/bulk-operations.md +386 -0
- package/clawpify/references/collections.md +71 -0
- package/clawpify/references/customers.md +141 -0
- package/clawpify/references/discounts.md +431 -0
- package/clawpify/references/draft-orders.md +495 -0
- package/clawpify/references/files.md +355 -0
- package/clawpify/references/fulfillments.md +437 -0
- package/clawpify/references/gift-cards.md +453 -0
- package/clawpify/references/inventory.md +107 -0
- package/clawpify/references/locations.md +349 -0
- package/clawpify/references/marketing.md +352 -0
- package/clawpify/references/markets.md +346 -0
- package/clawpify/references/menus.md +313 -0
- package/clawpify/references/metafields.md +461 -0
- package/clawpify/references/orders.md +164 -0
- package/clawpify/references/pages.md +308 -0
- package/clawpify/references/products.md +277 -0
- package/clawpify/references/refunds.md +401 -0
- package/clawpify/references/segments.md +319 -0
- package/clawpify/references/shipping.md +406 -0
- package/clawpify/references/shop.md +307 -0
- package/clawpify/references/subscriptions.md +429 -0
- package/clawpify/references/translations.md +270 -0
- package/clawpify/references/webhooks.md +400 -0
- package/dist/agent.d.ts +18 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +100 -0
- package/dist/auth.d.ts +34 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +58 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +236 -0
- package/dist/shopify.d.ts +29 -0
- package/dist/shopify.d.ts.map +1 -0
- package/dist/shopify.js +41 -0
- package/dist/skills.d.ts +8 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +36 -0
- package/package.json +100 -0
- package/src/agent.ts +133 -0
- package/src/auth.ts +109 -0
- package/src/index.ts +55 -0
- package/src/mcp-server.ts +190 -0
- package/src/shopify.ts +63 -0
- package/src/skills.ts +42 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/mcp-server.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListResourcesRequestSchema,
|
|
9
|
+
ReadResourceRequestSchema,
|
|
10
|
+
ListToolsRequestSchema
|
|
11
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
|
|
13
|
+
// src/auth.ts
|
|
14
|
+
async function getAccessToken(config) {
|
|
15
|
+
const storeUrl = config.storeUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
16
|
+
const response = await fetch(`https://${storeUrl}/admin/oauth/access_token`, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers: {
|
|
19
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
20
|
+
},
|
|
21
|
+
body: new URLSearchParams({
|
|
22
|
+
grant_type: "client_credentials",
|
|
23
|
+
client_id: config.clientId,
|
|
24
|
+
client_secret: config.clientSecret
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const text = await response.text();
|
|
29
|
+
throw new Error(`OAuth error (${response.status}): ${text}`);
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
return {
|
|
33
|
+
accessToken: data.access_token,
|
|
34
|
+
scope: data.scope,
|
|
35
|
+
expiresIn: data.expires_in
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async function createAuthenticatedConfig() {
|
|
39
|
+
const storeUrl = process.env.SHOPIFY_STORE_URL;
|
|
40
|
+
const directToken = process.env.SHOPIFY_ACCESS_TOKEN;
|
|
41
|
+
const clientId = process.env.SHOPIFY_CLIENT_ID;
|
|
42
|
+
const clientSecret = process.env.SHOPIFY_CLIENT_SECRET;
|
|
43
|
+
if (!storeUrl) {
|
|
44
|
+
throw new Error("Missing required environment variable: SHOPIFY_STORE_URL");
|
|
45
|
+
}
|
|
46
|
+
if (directToken) {
|
|
47
|
+
return {
|
|
48
|
+
storeUrl,
|
|
49
|
+
accessToken: directToken
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (clientId && clientSecret) {
|
|
53
|
+
const token = await getAccessToken({ storeUrl, clientId, clientSecret });
|
|
54
|
+
return {
|
|
55
|
+
storeUrl,
|
|
56
|
+
accessToken: token.accessToken,
|
|
57
|
+
scope: token.scope,
|
|
58
|
+
expiresIn: token.expiresIn
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`Missing authentication credentials. Provide either:
|
|
62
|
+
` + ` - SHOPIFY_ACCESS_TOKEN (direct token), or
|
|
63
|
+
` + " - SHOPIFY_CLIENT_ID and SHOPIFY_CLIENT_SECRET (OAuth client credentials)");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/shopify.ts
|
|
67
|
+
class ShopifyClient {
|
|
68
|
+
storeUrl;
|
|
69
|
+
accessToken;
|
|
70
|
+
apiVersion;
|
|
71
|
+
constructor(config) {
|
|
72
|
+
this.storeUrl = config.storeUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
73
|
+
this.accessToken = config.accessToken;
|
|
74
|
+
this.apiVersion = config.apiVersion ?? "2026-01";
|
|
75
|
+
}
|
|
76
|
+
async graphql(query, variables) {
|
|
77
|
+
const url = `https://${this.storeUrl}/admin/api/${this.apiVersion}/graphql.json`;
|
|
78
|
+
const response = await fetch(url, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: {
|
|
81
|
+
"Content-Type": "application/json",
|
|
82
|
+
"X-Shopify-Access-Token": this.accessToken
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify({ query, variables })
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const text = await response.text();
|
|
88
|
+
throw new Error(`Shopify API error (${response.status}): ${text}`);
|
|
89
|
+
}
|
|
90
|
+
return response.json();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/mcp-server.ts
|
|
95
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
96
|
+
import { join, dirname } from "node:path";
|
|
97
|
+
import { fileURLToPath } from "node:url";
|
|
98
|
+
import { homedir } from "node:os";
|
|
99
|
+
import { existsSync } from "node:fs";
|
|
100
|
+
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
101
|
+
var skillsDir = join(__dirname2, "..", "clawpify");
|
|
102
|
+
var configDir = join(homedir(), ".clawpify");
|
|
103
|
+
var envPath = join(configDir, ".env");
|
|
104
|
+
try {
|
|
105
|
+
if (existsSync(envPath)) {
|
|
106
|
+
const envContent = await readFile(envPath, "utf-8");
|
|
107
|
+
for (const line of envContent.split(`
|
|
108
|
+
`)) {
|
|
109
|
+
const trimmed = line.trim();
|
|
110
|
+
if (trimmed && !trimmed.startsWith("#")) {
|
|
111
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
112
|
+
const value = valueParts.join("=");
|
|
113
|
+
if (key && value && !process.env[key]) {
|
|
114
|
+
process.env[key] = value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
console.error(`Loaded config from ${envPath}`);
|
|
119
|
+
}
|
|
120
|
+
} catch {}
|
|
121
|
+
var shopifyClient = null;
|
|
122
|
+
async function getShopifyClient() {
|
|
123
|
+
if (!shopifyClient) {
|
|
124
|
+
const config = await createAuthenticatedConfig();
|
|
125
|
+
shopifyClient = new ShopifyClient({
|
|
126
|
+
storeUrl: config.storeUrl,
|
|
127
|
+
accessToken: config.accessToken
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return shopifyClient;
|
|
131
|
+
}
|
|
132
|
+
var server = new Server({
|
|
133
|
+
name: "clawpify",
|
|
134
|
+
version: "1.0.0"
|
|
135
|
+
}, {
|
|
136
|
+
capabilities: {
|
|
137
|
+
tools: {},
|
|
138
|
+
resources: {}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
142
|
+
tools: [
|
|
143
|
+
{
|
|
144
|
+
name: "shopify_graphql",
|
|
145
|
+
description: "Execute GraphQL queries against Shopify Admin API. Use this to query products, orders, customers, inventory, and manage store data.",
|
|
146
|
+
inputSchema: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
query: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "GraphQL query or mutation"
|
|
152
|
+
},
|
|
153
|
+
variables: {
|
|
154
|
+
type: "object",
|
|
155
|
+
description: "GraphQL variables (optional)"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
required: ["query"]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}));
|
|
163
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
164
|
+
if (request.params.name === "shopify_graphql") {
|
|
165
|
+
const { query, variables } = request.params.arguments;
|
|
166
|
+
try {
|
|
167
|
+
const client = await getShopifyClient();
|
|
168
|
+
const result = await client.graphql(query, variables);
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: JSON.stringify(result, null, 2)
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
content: [
|
|
180
|
+
{
|
|
181
|
+
type: "text",
|
|
182
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
isError: true
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
190
|
+
});
|
|
191
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
192
|
+
try {
|
|
193
|
+
const files = await readdir(skillsDir);
|
|
194
|
+
const resources = files.filter((f) => f.endsWith(".md")).map((f) => ({
|
|
195
|
+
uri: `shopify://skills/${f.replace(".md", "")}`,
|
|
196
|
+
name: f.replace(".md", ""),
|
|
197
|
+
mimeType: "text/markdown",
|
|
198
|
+
description: `Shopify ${f.replace(".md", "")} skill documentation`
|
|
199
|
+
}));
|
|
200
|
+
return { resources };
|
|
201
|
+
} catch {
|
|
202
|
+
return { resources: [] };
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
206
|
+
const uri = request.params.uri;
|
|
207
|
+
const match = uri.match(/^shopify:\/\/skills\/(.+)$/);
|
|
208
|
+
if (!match) {
|
|
209
|
+
throw new Error(`Invalid resource URI: ${uri}`);
|
|
210
|
+
}
|
|
211
|
+
const filename = `${match[1]}.md`;
|
|
212
|
+
const filepath = join(skillsDir, filename);
|
|
213
|
+
try {
|
|
214
|
+
const content = await readFile(filepath, "utf-8");
|
|
215
|
+
return {
|
|
216
|
+
contents: [
|
|
217
|
+
{
|
|
218
|
+
uri,
|
|
219
|
+
mimeType: "text/markdown",
|
|
220
|
+
text: content
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
};
|
|
224
|
+
} catch {
|
|
225
|
+
throw new Error(`Failed to read resource: ${filename}`);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
async function main() {
|
|
229
|
+
const transport = new StdioServerTransport;
|
|
230
|
+
await server.connect(transport);
|
|
231
|
+
console.error("Clawpify MCP server running on stdio");
|
|
232
|
+
}
|
|
233
|
+
main().catch((error) => {
|
|
234
|
+
console.error("Fatal error:", error);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface ShopifyClientConfig {
|
|
2
|
+
/** Shopify store URL (e.g. "my-store.myshopify.com") */
|
|
3
|
+
storeUrl: string;
|
|
4
|
+
/** Admin API access token */
|
|
5
|
+
accessToken: string;
|
|
6
|
+
/** API version (defaults to "2026-01") */
|
|
7
|
+
apiVersion?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface GraphQLResponse<T = any> {
|
|
10
|
+
data?: T;
|
|
11
|
+
errors?: Array<{
|
|
12
|
+
message: string;
|
|
13
|
+
locations?: Array<{
|
|
14
|
+
line: number;
|
|
15
|
+
column: number;
|
|
16
|
+
}>;
|
|
17
|
+
}>;
|
|
18
|
+
extensions?: Record<string, any>;
|
|
19
|
+
}
|
|
20
|
+
export declare class ShopifyClient {
|
|
21
|
+
private storeUrl;
|
|
22
|
+
private accessToken;
|
|
23
|
+
private apiVersion;
|
|
24
|
+
constructor(config: ShopifyClientConfig);
|
|
25
|
+
/** Execute a GraphQL query or mutation against the Shopify Admin API */
|
|
26
|
+
graphql<T = any>(query: string, variables?: Record<string, any>): Promise<GraphQLResponse<T>>;
|
|
27
|
+
}
|
|
28
|
+
export declare function createShopifyClient(): ShopifyClient;
|
|
29
|
+
//# sourceMappingURL=shopify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shopify.d.ts","sourceRoot":"","sources":["../src/shopify.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,GAAG;IACtC,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,mBAAmB;IAMvC,wEAAwE;IAClE,OAAO,CAAC,CAAC,GAAG,GAAG,EACnB,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC9B,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;CAmB/B;AAED,wBAAgB,mBAAmB,kBAWlC"}
|
package/dist/shopify.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/shopify.ts
|
|
2
|
+
class ShopifyClient {
|
|
3
|
+
storeUrl;
|
|
4
|
+
accessToken;
|
|
5
|
+
apiVersion;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.storeUrl = config.storeUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
8
|
+
this.accessToken = config.accessToken;
|
|
9
|
+
this.apiVersion = config.apiVersion ?? "2026-01";
|
|
10
|
+
}
|
|
11
|
+
async graphql(query, variables) {
|
|
12
|
+
const url = `https://${this.storeUrl}/admin/api/${this.apiVersion}/graphql.json`;
|
|
13
|
+
const response = await fetch(url, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
"X-Shopify-Access-Token": this.accessToken
|
|
18
|
+
},
|
|
19
|
+
body: JSON.stringify({ query, variables })
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
const text = await response.text();
|
|
23
|
+
throw new Error(`Shopify API error (${response.status}): ${text}`);
|
|
24
|
+
}
|
|
25
|
+
return response.json();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function createShopifyClient() {
|
|
29
|
+
const storeUrl = process.env.SHOPIFY_STORE_URL;
|
|
30
|
+
const accessToken = process.env.SHOPIFY_ACCESS_TOKEN;
|
|
31
|
+
if (!storeUrl || !accessToken) {
|
|
32
|
+
throw new Error("Missing required environment variables: SHOPIFY_STORE_URL and SHOPIFY_ACCESS_TOKEN");
|
|
33
|
+
}
|
|
34
|
+
return new ShopifyClient({ storeUrl, accessToken });
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
createShopifyClient,
|
|
38
|
+
ShopifyClient
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { ShopifyClient, createShopifyClient };
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load all skill files from the clawpify skills directory.
|
|
3
|
+
* Works both locally and when installed as an npm package.
|
|
4
|
+
*
|
|
5
|
+
* Optionally provide a custom directory path to load skills from.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadSkills(customDir?: string): Promise<string>;
|
|
8
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASpE"}
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/skills.ts
|
|
2
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
async function loadSkills(customDir) {
|
|
6
|
+
const baseDir = customDir ?? join(fileURLToPath(import.meta.url), "..", "..", "clawpify");
|
|
7
|
+
const skillParts = [];
|
|
8
|
+
await collectMarkdown(baseDir, skillParts);
|
|
9
|
+
return skillParts.join(`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
`);
|
|
14
|
+
}
|
|
15
|
+
async function collectMarkdown(dir, parts) {
|
|
16
|
+
let entries;
|
|
17
|
+
try {
|
|
18
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const fullPath = join(dir, entry.name);
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
await collectMarkdown(fullPath, parts);
|
|
26
|
+
} else if (entry.name.endsWith(".md")) {
|
|
27
|
+
const content = await readFile(fullPath, "utf-8");
|
|
28
|
+
parts.push(content);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
loadSkills
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { loadSkills };
|
package/package.json
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clawpify/skills",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Shopify Agent SDK — query and manage Shopify stores via GraphQL Admin API with AI agents and MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"bun": "./src/index.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./agent": {
|
|
16
|
+
"types": "./dist/agent.d.ts",
|
|
17
|
+
"bun": "./src/agent.ts",
|
|
18
|
+
"import": "./dist/agent.js",
|
|
19
|
+
"default": "./dist/agent.js"
|
|
20
|
+
},
|
|
21
|
+
"./client": {
|
|
22
|
+
"types": "./dist/shopify.d.ts",
|
|
23
|
+
"bun": "./src/shopify.ts",
|
|
24
|
+
"import": "./dist/shopify.js",
|
|
25
|
+
"default": "./dist/shopify.js"
|
|
26
|
+
},
|
|
27
|
+
"./auth": {
|
|
28
|
+
"types": "./dist/auth.d.ts",
|
|
29
|
+
"bun": "./src/auth.ts",
|
|
30
|
+
"import": "./dist/auth.js",
|
|
31
|
+
"default": "./dist/auth.js"
|
|
32
|
+
},
|
|
33
|
+
"./skills": {
|
|
34
|
+
"types": "./dist/skills.d.ts",
|
|
35
|
+
"bun": "./src/skills.ts",
|
|
36
|
+
"import": "./dist/skills.js",
|
|
37
|
+
"default": "./dist/skills.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"bin": {
|
|
41
|
+
"clawpify": "dist/mcp-server.js"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"src",
|
|
46
|
+
"clawpify"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "bun run build:clean && bun run build:js && bun run build:bin && bun run build:types",
|
|
50
|
+
"build:clean": "rm -rf dist",
|
|
51
|
+
"build:js": "bun build ./src/index.ts ./src/agent.ts ./src/shopify.ts ./src/auth.ts ./src/skills.ts --outdir ./dist --target node --format esm --splitting --external @anthropic-ai/sdk --external @modelcontextprotocol/sdk",
|
|
52
|
+
"build:bin": "bun build ./src/mcp-server.ts --outdir ./dist --target node --format esm --external @anthropic-ai/sdk --external @modelcontextprotocol/sdk",
|
|
53
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
54
|
+
"prepublishOnly": "bun run build",
|
|
55
|
+
"start": "bun run examples/server.ts"
|
|
56
|
+
},
|
|
57
|
+
"keywords": [
|
|
58
|
+
"shopify",
|
|
59
|
+
"ai",
|
|
60
|
+
"mcp",
|
|
61
|
+
"graphql",
|
|
62
|
+
"agent",
|
|
63
|
+
"sdk",
|
|
64
|
+
"claude",
|
|
65
|
+
"anthropic",
|
|
66
|
+
"model-context-protocol"
|
|
67
|
+
],
|
|
68
|
+
"license": "MIT",
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"repository": {
|
|
73
|
+
"type": "git",
|
|
74
|
+
"url": "git+https://github.com/clawpify/skills.git"
|
|
75
|
+
},
|
|
76
|
+
"homepage": "https://github.com/clawpify/skills#readme",
|
|
77
|
+
"bugs": {
|
|
78
|
+
"url": "https://github.com/clawpify/skills/issues"
|
|
79
|
+
},
|
|
80
|
+
"engines": {
|
|
81
|
+
"node": ">=18.0.0"
|
|
82
|
+
},
|
|
83
|
+
"sideEffects": false,
|
|
84
|
+
"dependencies": {
|
|
85
|
+
"@modelcontextprotocol/sdk": "^1.26.0"
|
|
86
|
+
},
|
|
87
|
+
"peerDependencies": {
|
|
88
|
+
"@anthropic-ai/sdk": ">=0.39.0"
|
|
89
|
+
},
|
|
90
|
+
"peerDependenciesMeta": {
|
|
91
|
+
"@anthropic-ai/sdk": {
|
|
92
|
+
"optional": true
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"devDependencies": {
|
|
96
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
97
|
+
"bun-types": "latest",
|
|
98
|
+
"typescript": "^5.0.0"
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/agent.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { ShopifyClient } from "./shopify";
|
|
3
|
+
|
|
4
|
+
const SHOPIFY_GRAPHQL_TOOL: Anthropic.Tool = {
|
|
5
|
+
name: "shopify_graphql",
|
|
6
|
+
description:
|
|
7
|
+
"Execute a GraphQL query against the Shopify Admin API. Use this to interact with products, orders, customers, inventory, and other Shopify resources.",
|
|
8
|
+
input_schema: {
|
|
9
|
+
type: "object" as const,
|
|
10
|
+
properties: {
|
|
11
|
+
query: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "The GraphQL query or mutation to execute",
|
|
14
|
+
},
|
|
15
|
+
variables: {
|
|
16
|
+
type: "object",
|
|
17
|
+
description: "Optional variables for the GraphQL query",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
required: ["query"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export class ShopifyAgent {
|
|
25
|
+
private anthropic: Anthropic;
|
|
26
|
+
private shopify: ShopifyClient;
|
|
27
|
+
private skillContent: string;
|
|
28
|
+
private model: string;
|
|
29
|
+
|
|
30
|
+
constructor(config: {
|
|
31
|
+
shopify: ShopifyClient;
|
|
32
|
+
skillContent: string;
|
|
33
|
+
model?: string;
|
|
34
|
+
}) {
|
|
35
|
+
this.anthropic = new Anthropic();
|
|
36
|
+
this.shopify = config.shopify;
|
|
37
|
+
this.skillContent = config.skillContent;
|
|
38
|
+
this.model = config.model ?? "claude-sonnet-4-5";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async chat(
|
|
42
|
+
userMessage: string,
|
|
43
|
+
conversationHistory: Anthropic.MessageParam[] = []
|
|
44
|
+
): Promise<{ response: string; history: Anthropic.MessageParam[] }> {
|
|
45
|
+
const systemPrompt = `You are a helpful Shopify assistant that can query and manage a Shopify store using the GraphQL Admin API.
|
|
46
|
+
|
|
47
|
+
${this.skillContent}
|
|
48
|
+
|
|
49
|
+
When the user asks about products, orders, customers, or any Shopify data, use the shopify_graphql tool to fetch or modify the data. Always explain what you're doing and present results clearly.`;
|
|
50
|
+
|
|
51
|
+
const messages: Anthropic.MessageParam[] = [
|
|
52
|
+
...conversationHistory,
|
|
53
|
+
{ role: "user", content: userMessage },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
let response = await this.anthropic.messages.create({
|
|
57
|
+
model: this.model,
|
|
58
|
+
max_tokens: 4096,
|
|
59
|
+
system: systemPrompt,
|
|
60
|
+
tools: [SHOPIFY_GRAPHQL_TOOL],
|
|
61
|
+
messages,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const assistantMessages: Anthropic.ContentBlockParam[] = [];
|
|
65
|
+
|
|
66
|
+
// Agentic loop: keep processing tool calls until we get a final response
|
|
67
|
+
while (response.stop_reason === "tool_use") {
|
|
68
|
+
const toolUseBlocks = response.content.filter(
|
|
69
|
+
(block: Anthropic.ContentBlock): block is Anthropic.ToolUseBlock =>
|
|
70
|
+
block.type === "tool_use"
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
assistantMessages.push(...(response.content as Anthropic.ContentBlockParam[]));
|
|
74
|
+
|
|
75
|
+
const toolResults: Anthropic.ToolResultBlockParam[] = [];
|
|
76
|
+
|
|
77
|
+
for (const toolUse of toolUseBlocks) {
|
|
78
|
+
if (toolUse.name === "shopify_graphql") {
|
|
79
|
+
const input = toolUse.input as {
|
|
80
|
+
query: string;
|
|
81
|
+
variables?: Record<string, any>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const result = await this.shopify.graphql(
|
|
86
|
+
input.query,
|
|
87
|
+
input.variables
|
|
88
|
+
);
|
|
89
|
+
toolResults.push({
|
|
90
|
+
type: "tool_result",
|
|
91
|
+
tool_use_id: toolUse.id,
|
|
92
|
+
content: JSON.stringify(result, null, 2),
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
toolResults.push({
|
|
96
|
+
type: "tool_result",
|
|
97
|
+
tool_use_id: toolUse.id,
|
|
98
|
+
content: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
99
|
+
is_error: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
messages.push({ role: "assistant", content: assistantMessages.slice() });
|
|
106
|
+
messages.push({ role: "user", content: toolResults });
|
|
107
|
+
assistantMessages.length = 0;
|
|
108
|
+
|
|
109
|
+
response = await this.anthropic.messages.create({
|
|
110
|
+
model: this.model,
|
|
111
|
+
max_tokens: 4096,
|
|
112
|
+
system: systemPrompt,
|
|
113
|
+
tools: [SHOPIFY_GRAPHQL_TOOL],
|
|
114
|
+
messages,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Extract final text response
|
|
119
|
+
const textBlocks = response.content.filter(
|
|
120
|
+
(block: Anthropic.ContentBlock): block is Anthropic.TextBlock =>
|
|
121
|
+
block.type === "text"
|
|
122
|
+
);
|
|
123
|
+
const finalResponse = textBlocks.map((b: Anthropic.TextBlock) => b.text).join("\n");
|
|
124
|
+
|
|
125
|
+
// Build updated history
|
|
126
|
+
const updatedHistory: Anthropic.MessageParam[] = [
|
|
127
|
+
...messages,
|
|
128
|
+
{ role: "assistant", content: response.content },
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
return { response: finalResponse, history: updatedHistory };
|
|
132
|
+
}
|
|
133
|
+
}
|