directify-mcp 1.0.0 → 1.1.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 (3) hide show
  1. package/README.md +73 -0
  2. package/package.json +1 -1
  3. package/src/tools.js +139 -0
package/README.md CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  This MCP server lets AI assistants directly manage your Directify directories. Ask Claude to create listings, update categories, publish articles, and more - all through natural language.
8
8
 
9
+ You can use it in two ways:
10
+ - **Local** - Install the npm package and run it on your machine (see [Installation](#installation))
11
+ - **Remote** - Use the hosted server with zero installation (see [Remote Server](#remote-server-no-installation-required))
12
+
9
13
  ## Installation
10
14
 
11
15
  ```bash
@@ -62,6 +66,52 @@ Add to `.cursor/mcp.json` in your project:
62
66
  }
63
67
  ```
64
68
 
69
+ ## Remote Server (No Installation Required)
70
+
71
+ If you prefer not to install anything locally, you can use the hosted remote MCP server. This works with any MCP client that supports remote servers via `mcp-remote`.
72
+
73
+ ### Claude Desktop (Remote)
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "directify": {
79
+ "command": "npx",
80
+ "args": [
81
+ "-y", "mcp-remote",
82
+ "https://mcp.directify.app/mcp",
83
+ "--header", "Authorization:Bearer YOUR_API_TOKEN",
84
+ "--header", "X-Directory-ID:YOUR_DIRECTORY_ID"
85
+ ]
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### Cursor (Remote)
92
+
93
+ Add to `.cursor/mcp.json`:
94
+
95
+ ```json
96
+ {
97
+ "mcpServers": {
98
+ "directify": {
99
+ "command": "npx",
100
+ "args": [
101
+ "-y", "mcp-remote",
102
+ "https://mcp.directify.app/mcp",
103
+ "--header", "Authorization:Bearer YOUR_API_TOKEN",
104
+ "--header", "X-Directory-ID:YOUR_DIRECTORY_ID"
105
+ ]
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ The `X-Directory-ID` header is optional. If omitted, the AI will ask which directory to use (or you can call `list_directories` to discover them).
112
+
113
+ ---
114
+
65
115
  ## Configuration
66
116
 
67
117
  | Environment Variable | Required | Description |
@@ -137,6 +187,17 @@ Ask Claude: *"List my Directify directories"* - or find it in the URL when viewi
137
187
  | `delete_article` | Delete an article |
138
188
  | `toggle_article` | Toggle active/inactive status |
139
189
 
190
+ ### Custom Pages
191
+
192
+ | Tool | Description |
193
+ |------|-------------|
194
+ | `list_pages` | List all custom pages |
195
+ | `get_page` | Get page details |
196
+ | `create_page` | Create a custom page (markdown content, placement, SEO) |
197
+ | `update_page` | Update a page |
198
+ | `delete_page` | Delete a page |
199
+ | `toggle_page` | Toggle published/unpublished status |
200
+
140
201
  ## Example Conversations
141
202
 
142
203
  ### Create a listing
@@ -163,6 +224,18 @@ Claude will use `create_article` with markdown content.
163
224
 
164
225
  Claude will use `list_listings`, then `update_listing` for each one.
165
226
 
227
+ ### Create programmatic SEO pages
228
+
229
+ > **You:** Create comparison pages for "NYC vs Chicago pizza", "NYC vs LA tacos", and "NYC vs Boston seafood" with SEO titles and descriptions.
230
+
231
+ Claude will use `create_page` for each with markdown content, `unlisted` placement, and SEO metadata.
232
+
233
+ ### Manage custom pages
234
+
235
+ > **You:** Add an About Us page to the navbar and a Terms of Service page to the footer.
236
+
237
+ Claude will use `create_page` with `placement: "navbar"` and `placement: "footer"`.
238
+
166
239
  ## Rate Limits
167
240
 
168
241
  The Directify API allows **120 requests per minute** per directory. The MCP server handles rate limit errors gracefully and will inform the AI assistant to retry.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directify-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "MCP server for Directify - manage your directory websites through AI assistants like Claude.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/tools.js CHANGED
@@ -593,6 +593,139 @@ export const toggleArticle = {
593
593
  },
594
594
  };
595
595
 
596
+ // ─── Custom Pages ───
597
+
598
+ export const listPages = {
599
+ name: 'list_pages',
600
+ description: 'List all custom pages in a directory. Custom pages are for static content like About, Terms, comparison pages, etc.',
601
+ inputSchema: {
602
+ type: 'object',
603
+ properties: {
604
+ directory_id: { type: 'string', description: 'Directory ID' },
605
+ },
606
+ },
607
+ handler: async ({ directory_id }) => {
608
+ const dir = resolveDirectory(directory_id);
609
+ const data = await api.get(`/directories/${dir}/pages`);
610
+ return data;
611
+ },
612
+ };
613
+
614
+ export const getPage = {
615
+ name: 'get_page',
616
+ description: 'Get full details of a specific custom page.',
617
+ inputSchema: {
618
+ type: 'object',
619
+ properties: {
620
+ directory_id: { type: 'string', description: 'Directory ID' },
621
+ page_id: { type: 'string', description: 'Page ID' },
622
+ },
623
+ required: ['page_id'],
624
+ },
625
+ handler: async ({ directory_id, page_id }) => {
626
+ const dir = resolveDirectory(directory_id);
627
+ const data = await api.get(`/directories/${dir}/pages/${page_id}`);
628
+ return data.data || data;
629
+ },
630
+ };
631
+
632
+ export const createPage = {
633
+ name: 'create_page',
634
+ description: 'Create a custom page. Great for programmatic SEO pages (comparisons, location pages), About, Terms, etc. Content is markdown.',
635
+ inputSchema: {
636
+ type: 'object',
637
+ properties: {
638
+ directory_id: { type: 'string', description: 'Directory ID' },
639
+ title: { type: 'string', description: 'Page title (required)' },
640
+ slug: { type: 'string', description: 'URL slug (auto-generated from title if not set)' },
641
+ markdown: { type: 'string', description: 'Page content in markdown' },
642
+ placement: { type: 'string', enum: ['navbar', 'footer', 'sidebar', 'unlisted'], description: 'Where the page link appears (default: unlisted)' },
643
+ is_published: { type: 'boolean', description: 'Published status (default: true)' },
644
+ order: { type: 'number', description: 'Sort order for navigation' },
645
+ seo_title: { type: 'string', description: 'SEO title' },
646
+ seo_description: { type: 'string', description: 'SEO meta description' },
647
+ },
648
+ required: ['title'],
649
+ },
650
+ handler: async ({ directory_id, seo_title, seo_description, ...body }) => {
651
+ const dir = resolveDirectory(directory_id);
652
+ if (seo_title || seo_description) {
653
+ body.seo = {};
654
+ if (seo_title) body.seo.title = seo_title;
655
+ if (seo_description) body.seo.description = seo_description;
656
+ }
657
+ const data = await api.post(`/directories/${dir}/pages`, body);
658
+ return data.data || data;
659
+ },
660
+ };
661
+
662
+ export const updatePage = {
663
+ name: 'update_page',
664
+ description: 'Update an existing custom page. Only pass fields you want to change.',
665
+ inputSchema: {
666
+ type: 'object',
667
+ properties: {
668
+ directory_id: { type: 'string', description: 'Directory ID' },
669
+ page_id: { type: 'string', description: 'Page ID to update' },
670
+ title: { type: 'string', description: 'Page title' },
671
+ slug: { type: 'string', description: 'URL slug' },
672
+ markdown: { type: 'string', description: 'Page content in markdown' },
673
+ placement: { type: 'string', enum: ['navbar', 'footer', 'sidebar', 'unlisted'], description: 'Where the page link appears' },
674
+ is_published: { type: 'boolean', description: 'Published status' },
675
+ order: { type: 'number', description: 'Sort order for navigation' },
676
+ seo_title: { type: 'string', description: 'SEO title' },
677
+ seo_description: { type: 'string', description: 'SEO meta description' },
678
+ },
679
+ required: ['page_id'],
680
+ },
681
+ handler: async ({ directory_id, page_id, seo_title, seo_description, ...body }) => {
682
+ const dir = resolveDirectory(directory_id);
683
+ if (seo_title || seo_description) {
684
+ body.seo = {};
685
+ if (seo_title) body.seo.title = seo_title;
686
+ if (seo_description) body.seo.description = seo_description;
687
+ }
688
+ const data = await api.put(`/directories/${dir}/pages/${page_id}`, body);
689
+ return data.data || data;
690
+ },
691
+ };
692
+
693
+ export const deletePage = {
694
+ name: 'delete_page',
695
+ description: 'Delete a custom page from a directory.',
696
+ inputSchema: {
697
+ type: 'object',
698
+ properties: {
699
+ directory_id: { type: 'string', description: 'Directory ID' },
700
+ page_id: { type: 'string', description: 'Page ID to delete' },
701
+ },
702
+ required: ['page_id'],
703
+ },
704
+ handler: async ({ directory_id, page_id }) => {
705
+ const dir = resolveDirectory(directory_id);
706
+ await api.delete(`/directories/${dir}/pages/${page_id}`);
707
+ return { success: true, message: `Page ${page_id} deleted.` };
708
+ },
709
+ };
710
+
711
+ export const togglePage = {
712
+ name: 'toggle_page',
713
+ description: 'Toggle a custom page between published and unpublished status.',
714
+ inputSchema: {
715
+ type: 'object',
716
+ properties: {
717
+ directory_id: { type: 'string', description: 'Directory ID' },
718
+ page_id: { type: 'string', description: 'Page ID to toggle' },
719
+ },
720
+ required: ['page_id'],
721
+ },
722
+ handler: async ({ directory_id, page_id }) => {
723
+ const dir = resolveDirectory(directory_id);
724
+ const data = await api.patch(`/directories/${dir}/pages/${page_id}/toggle`);
725
+ return data.data || data;
726
+ },
727
+ };
728
+
596
729
  // ─── Export all tools ───
597
730
 
598
731
  export const allTools = [
@@ -621,4 +754,10 @@ export const allTools = [
621
754
  updateArticle,
622
755
  deleteArticle,
623
756
  toggleArticle,
757
+ listPages,
758
+ getPage,
759
+ createPage,
760
+ updatePage,
761
+ deletePage,
762
+ togglePage,
624
763
  ];