@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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/clawpify/SKILL.md +134 -0
  4. package/clawpify/references/blogs.md +385 -0
  5. package/clawpify/references/bulk-operations.md +386 -0
  6. package/clawpify/references/collections.md +71 -0
  7. package/clawpify/references/customers.md +141 -0
  8. package/clawpify/references/discounts.md +431 -0
  9. package/clawpify/references/draft-orders.md +495 -0
  10. package/clawpify/references/files.md +355 -0
  11. package/clawpify/references/fulfillments.md +437 -0
  12. package/clawpify/references/gift-cards.md +453 -0
  13. package/clawpify/references/inventory.md +107 -0
  14. package/clawpify/references/locations.md +349 -0
  15. package/clawpify/references/marketing.md +352 -0
  16. package/clawpify/references/markets.md +346 -0
  17. package/clawpify/references/menus.md +313 -0
  18. package/clawpify/references/metafields.md +461 -0
  19. package/clawpify/references/orders.md +164 -0
  20. package/clawpify/references/pages.md +308 -0
  21. package/clawpify/references/products.md +277 -0
  22. package/clawpify/references/refunds.md +401 -0
  23. package/clawpify/references/segments.md +319 -0
  24. package/clawpify/references/shipping.md +406 -0
  25. package/clawpify/references/shop.md +307 -0
  26. package/clawpify/references/subscriptions.md +429 -0
  27. package/clawpify/references/translations.md +270 -0
  28. package/clawpify/references/webhooks.md +400 -0
  29. package/dist/agent.d.ts +18 -0
  30. package/dist/agent.d.ts.map +1 -0
  31. package/dist/agent.js +100 -0
  32. package/dist/auth.d.ts +34 -0
  33. package/dist/auth.d.ts.map +1 -0
  34. package/dist/auth.js +58 -0
  35. package/dist/index.d.ts +41 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +22 -0
  38. package/dist/mcp-server.d.ts +3 -0
  39. package/dist/mcp-server.d.ts.map +1 -0
  40. package/dist/mcp-server.js +236 -0
  41. package/dist/shopify.d.ts +29 -0
  42. package/dist/shopify.d.ts.map +1 -0
  43. package/dist/shopify.js +41 -0
  44. package/dist/skills.d.ts +8 -0
  45. package/dist/skills.d.ts.map +1 -0
  46. package/dist/skills.js +36 -0
  47. package/package.json +100 -0
  48. package/src/agent.ts +133 -0
  49. package/src/auth.ts +109 -0
  50. package/src/index.ts +55 -0
  51. package/src/mcp-server.ts +190 -0
  52. package/src/shopify.ts +63 -0
  53. package/src/skills.ts +42 -0
package/src/auth.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Shopify OAuth Client Credentials
3
+ * For apps installed on stores you own (server-to-server)
4
+ */
5
+
6
+ export interface AccessTokenResponse {
7
+ accessToken: string;
8
+ scope: string;
9
+ expiresIn: number;
10
+ }
11
+
12
+ export interface ClientCredentialsConfig {
13
+ storeUrl: string;
14
+ clientId: string;
15
+ clientSecret: string;
16
+ }
17
+
18
+ /**
19
+ * Get an access token using the Client Credentials grant.
20
+ * Token expires in 24 hours and must be refreshed with the same request.
21
+ *
22
+ * @see https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/client-credentials-grant
23
+ */
24
+ export async function getAccessToken(
25
+ config: ClientCredentialsConfig
26
+ ): Promise<AccessTokenResponse> {
27
+ const storeUrl = config.storeUrl
28
+ .replace(/^https?:\/\//, "")
29
+ .replace(/\/$/, "");
30
+
31
+ const response = await fetch(
32
+ `https://${storeUrl}/admin/oauth/access_token`,
33
+ {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/x-www-form-urlencoded",
37
+ },
38
+ body: new URLSearchParams({
39
+ grant_type: "client_credentials",
40
+ client_id: config.clientId,
41
+ client_secret: config.clientSecret,
42
+ }),
43
+ }
44
+ );
45
+
46
+ if (!response.ok) {
47
+ const text = await response.text();
48
+ throw new Error(`OAuth error (${response.status}): ${text}`);
49
+ }
50
+
51
+ const data = (await response.json()) as {
52
+ access_token: string;
53
+ scope: string;
54
+ expires_in: number;
55
+ };
56
+
57
+ return {
58
+ accessToken: data.access_token,
59
+ scope: data.scope,
60
+ expiresIn: data.expires_in,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Create a Shopify client config from environment variables.
66
+ * Supports two authentication methods:
67
+ * 1. Direct access token (SHOPIFY_ACCESS_TOKEN) - uses token directly
68
+ * 2. Client credentials (SHOPIFY_CLIENT_ID + SHOPIFY_CLIENT_SECRET) - fetches token via OAuth
69
+ */
70
+ export async function createAuthenticatedConfig(): Promise<{
71
+ storeUrl: string;
72
+ accessToken: string;
73
+ scope?: string;
74
+ expiresIn?: number;
75
+ }> {
76
+ const storeUrl = process.env.SHOPIFY_STORE_URL;
77
+ const directToken = process.env.SHOPIFY_ACCESS_TOKEN;
78
+ const clientId = process.env.SHOPIFY_CLIENT_ID;
79
+ const clientSecret = process.env.SHOPIFY_CLIENT_SECRET;
80
+
81
+ if (!storeUrl) {
82
+ throw new Error("Missing required environment variable: SHOPIFY_STORE_URL");
83
+ }
84
+
85
+ // Option 1: Use direct access token if provided
86
+ if (directToken) {
87
+ return {
88
+ storeUrl,
89
+ accessToken: directToken,
90
+ };
91
+ }
92
+
93
+ // Option 2: Use client credentials OAuth flow
94
+ if (clientId && clientSecret) {
95
+ const token = await getAccessToken({ storeUrl, clientId, clientSecret });
96
+ return {
97
+ storeUrl,
98
+ accessToken: token.accessToken,
99
+ scope: token.scope,
100
+ expiresIn: token.expiresIn,
101
+ };
102
+ }
103
+
104
+ throw new Error(
105
+ "Missing authentication credentials. Provide either:\n" +
106
+ " - SHOPIFY_ACCESS_TOKEN (direct token), or\n" +
107
+ " - SHOPIFY_CLIENT_ID and SHOPIFY_CLIENT_SECRET (OAuth client credentials)"
108
+ );
109
+ }
package/src/index.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Clawpify - Shopify Agent SDK
3
+ *
4
+ * Query and manage Shopify stores via GraphQL Admin API with AI agents.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { ShopifyClient } from "clawpify";
9
+ *
10
+ * const client = new ShopifyClient({
11
+ * storeUrl: "my-store.myshopify.com",
12
+ * accessToken: "shpat_xxxxx",
13
+ * });
14
+ *
15
+ * const { data } = await client.graphql(`{
16
+ * products(first: 5) {
17
+ * nodes { id title }
18
+ * }
19
+ * }`);
20
+ * ```
21
+ *
22
+ * @example Using the AI agent (requires @anthropic-ai/sdk)
23
+ * ```ts
24
+ * import { ShopifyClient } from "clawpify";
25
+ * import { ShopifyAgent } from "clawpify/agent";
26
+ * import { loadSkills } from "clawpify/skills";
27
+ *
28
+ * const client = new ShopifyClient({ storeUrl, accessToken });
29
+ * const skills = await loadSkills();
30
+ * const agent = new ShopifyAgent({ shopify: client, skillContent: skills });
31
+ *
32
+ * const result = await agent.chat("List my products");
33
+ * console.log(result.response);
34
+ * ```
35
+ */
36
+
37
+ // Core client
38
+ export { ShopifyClient, createShopifyClient } from "./shopify";
39
+
40
+ // Authentication
41
+ export {
42
+ createAuthenticatedConfig,
43
+ getAccessToken,
44
+ type AccessTokenResponse,
45
+ type ClientCredentialsConfig,
46
+ } from "./auth";
47
+
48
+ // AI Agent (requires @anthropic-ai/sdk peer dependency)
49
+ export { ShopifyAgent } from "./agent";
50
+
51
+ // Skills loader
52
+ export { loadSkills } from "./skills";
53
+
54
+ // Re-export types for convenience
55
+ export type { ShopifyClientConfig, GraphQLResponse } from "./shopify";
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListResourcesRequestSchema,
8
+ ReadResourceRequestSchema,
9
+ ListToolsRequestSchema,
10
+ } from "@modelcontextprotocol/sdk/types.js";
11
+ import { createAuthenticatedConfig } from "./auth.js";
12
+ import { ShopifyClient } from "./shopify.js";
13
+ import { readdir, readFile } from "node:fs/promises";
14
+ import { join, dirname } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { homedir } from "node:os";
17
+ import { existsSync } from "node:fs";
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const skillsDir = join(__dirname, "..", "clawpify");
21
+
22
+ // Load .env from ~/.clawpify/.env if it exists (for MCP server mode)
23
+ const configDir = join(homedir(), ".clawpify");
24
+ const envPath = join(configDir, ".env");
25
+ try {
26
+ if (existsSync(envPath)) {
27
+ const envContent = await readFile(envPath, "utf-8");
28
+ for (const line of envContent.split("\n")) {
29
+ const trimmed = line.trim();
30
+ if (trimmed && !trimmed.startsWith("#")) {
31
+ const [key, ...valueParts] = trimmed.split("=");
32
+ const value = valueParts.join("=");
33
+ if (key && value && !process.env[key]) {
34
+ process.env[key] = value;
35
+ }
36
+ }
37
+ }
38
+ console.error(`Loaded config from ${envPath}`);
39
+ }
40
+ } catch {
41
+ // Ignore errors, will use env vars from MCP config or current .env
42
+ }
43
+
44
+ // Cache the authenticated client
45
+ let shopifyClient: ShopifyClient | null = null;
46
+
47
+ async function getShopifyClient(): Promise<ShopifyClient> {
48
+ if (!shopifyClient) {
49
+ const config = await createAuthenticatedConfig();
50
+ shopifyClient = new ShopifyClient({
51
+ storeUrl: config.storeUrl,
52
+ accessToken: config.accessToken,
53
+ });
54
+ }
55
+ return shopifyClient;
56
+ }
57
+
58
+ // Create MCP server
59
+ const server = new Server(
60
+ {
61
+ name: "clawpify",
62
+ version: "1.0.0",
63
+ },
64
+ {
65
+ capabilities: {
66
+ tools: {},
67
+ resources: {},
68
+ },
69
+ }
70
+ );
71
+
72
+ // List available tools
73
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
74
+ tools: [
75
+ {
76
+ name: "shopify_graphql",
77
+ description:
78
+ "Execute GraphQL queries against Shopify Admin API. Use this to query products, orders, customers, inventory, and manage store data.",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ query: {
83
+ type: "string",
84
+ description: "GraphQL query or mutation",
85
+ },
86
+ variables: {
87
+ type: "object",
88
+ description: "GraphQL variables (optional)",
89
+ },
90
+ },
91
+ required: ["query"],
92
+ },
93
+ },
94
+ ],
95
+ }));
96
+
97
+ // Handle tool calls
98
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
99
+ if (request.params.name === "shopify_graphql") {
100
+ const { query, variables } = request.params.arguments as {
101
+ query: string;
102
+ variables?: Record<string, any>;
103
+ };
104
+
105
+ try {
106
+ const client = await getShopifyClient();
107
+ const result = await client.graphql(query, variables);
108
+
109
+ return {
110
+ content: [
111
+ {
112
+ type: "text",
113
+ text: JSON.stringify(result, null, 2),
114
+ },
115
+ ],
116
+ };
117
+ } catch (error) {
118
+ return {
119
+ content: [
120
+ {
121
+ type: "text",
122
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
123
+ },
124
+ ],
125
+ isError: true,
126
+ };
127
+ }
128
+ }
129
+
130
+ throw new Error(`Unknown tool: ${request.params.name}`);
131
+ });
132
+
133
+ // List available resources (skill files)
134
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
135
+ try {
136
+ const files = await readdir(skillsDir);
137
+ const resources = files
138
+ .filter((f) => f.endsWith(".md"))
139
+ .map((f) => ({
140
+ uri: `shopify://skills/${f.replace(".md", "")}`,
141
+ name: f.replace(".md", ""),
142
+ mimeType: "text/markdown",
143
+ description: `Shopify ${f.replace(".md", "")} skill documentation`,
144
+ }));
145
+
146
+ return { resources };
147
+ } catch {
148
+ return { resources: [] };
149
+ }
150
+ });
151
+
152
+ // Read resource content
153
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
154
+ const uri = request.params.uri;
155
+ const match = uri.match(/^shopify:\/\/skills\/(.+)$/);
156
+
157
+ if (!match) {
158
+ throw new Error(`Invalid resource URI: ${uri}`);
159
+ }
160
+
161
+ const filename = `${match[1]}.md`;
162
+ const filepath = join(skillsDir, filename);
163
+
164
+ try {
165
+ const content = await readFile(filepath, "utf-8");
166
+ return {
167
+ contents: [
168
+ {
169
+ uri,
170
+ mimeType: "text/markdown",
171
+ text: content,
172
+ },
173
+ ],
174
+ };
175
+ } catch {
176
+ throw new Error(`Failed to read resource: ${filename}`);
177
+ }
178
+ });
179
+
180
+ // Start server
181
+ async function main() {
182
+ const transport = new StdioServerTransport();
183
+ await server.connect(transport);
184
+ console.error("Clawpify MCP server running on stdio");
185
+ }
186
+
187
+ main().catch((error) => {
188
+ console.error("Fatal error:", error);
189
+ process.exit(1);
190
+ });
package/src/shopify.ts ADDED
@@ -0,0 +1,63 @@
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
+
10
+ export interface GraphQLResponse<T = any> {
11
+ data?: T;
12
+ errors?: Array<{ message: string; locations?: Array<{ line: number; column: number }> }>;
13
+ extensions?: Record<string, any>;
14
+ }
15
+
16
+ export class ShopifyClient {
17
+ private storeUrl: string;
18
+ private accessToken: string;
19
+ private apiVersion: string;
20
+
21
+ constructor(config: ShopifyClientConfig) {
22
+ this.storeUrl = config.storeUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
23
+ this.accessToken = config.accessToken;
24
+ this.apiVersion = config.apiVersion ?? "2026-01";
25
+ }
26
+
27
+ /** Execute a GraphQL query or mutation against the Shopify Admin API */
28
+ async graphql<T = any>(
29
+ query: string,
30
+ variables?: Record<string, any>
31
+ ): Promise<GraphQLResponse<T>> {
32
+ const url = `https://${this.storeUrl}/admin/api/${this.apiVersion}/graphql.json`;
33
+
34
+ const response = await fetch(url, {
35
+ method: "POST",
36
+ headers: {
37
+ "Content-Type": "application/json",
38
+ "X-Shopify-Access-Token": this.accessToken,
39
+ },
40
+ body: JSON.stringify({ query, variables }),
41
+ });
42
+
43
+ if (!response.ok) {
44
+ const text = await response.text();
45
+ throw new Error(`Shopify API error (${response.status}): ${text}`);
46
+ }
47
+
48
+ return response.json() as Promise<GraphQLResponse<T>>;
49
+ }
50
+ }
51
+
52
+ export function createShopifyClient() {
53
+ const storeUrl = process.env.SHOPIFY_STORE_URL;
54
+ const accessToken = process.env.SHOPIFY_ACCESS_TOKEN;
55
+
56
+ if (!storeUrl || !accessToken) {
57
+ throw new Error(
58
+ "Missing required environment variables: SHOPIFY_STORE_URL and SHOPIFY_ACCESS_TOKEN"
59
+ );
60
+ }
61
+
62
+ return new ShopifyClient({ storeUrl, accessToken });
63
+ }
package/src/skills.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ /**
6
+ * Load all skill files from the clawpify skills directory.
7
+ * Works both locally and when installed as an npm package.
8
+ *
9
+ * Optionally provide a custom directory path to load skills from.
10
+ */
11
+ export async function loadSkills(customDir?: string): Promise<string> {
12
+ const baseDir =
13
+ customDir ??
14
+ join(fileURLToPath(import.meta.url), "..", "..", "clawpify");
15
+
16
+ const skillParts: string[] = [];
17
+ await collectMarkdown(baseDir, skillParts);
18
+
19
+ return skillParts.join("\n\n---\n\n");
20
+ }
21
+
22
+ async function collectMarkdown(
23
+ dir: string,
24
+ parts: string[]
25
+ ): Promise<void> {
26
+ let entries;
27
+ try {
28
+ entries = await readdir(dir, { withFileTypes: true });
29
+ } catch {
30
+ return;
31
+ }
32
+
33
+ for (const entry of entries) {
34
+ const fullPath = join(dir, entry.name);
35
+ if (entry.isDirectory()) {
36
+ await collectMarkdown(fullPath, parts);
37
+ } else if (entry.name.endsWith(".md")) {
38
+ const content = await readFile(fullPath, "utf-8");
39
+ parts.push(content);
40
+ }
41
+ }
42
+ }