directify-mcp 1.3.1 → 1.4.0

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 +34 -2
  2. package/package.json +1 -1
  3. package/src/tools.js +127 -2
package/README.md CHANGED
@@ -163,6 +163,10 @@ Ask Claude: *"List my Directify directories"* - or find it in the URL when viewi
163
163
  | Tool | Description |
164
164
  |------|-------------|
165
165
  | `list_custom_fields` | List all custom fields (useful to know field names for listings) |
166
+ | `get_custom_field` | Get a custom field definition |
167
+ | `create_custom_field` | Create a custom field definition (text, number, select, etc.) |
168
+ | `update_custom_field` | Update a custom field definition |
169
+ | `delete_custom_field` | Delete a custom field (also removes its values from listings) |
166
170
 
167
171
  ### Listings
168
172
 
@@ -170,12 +174,14 @@ Ask Claude: *"List my Directify directories"* - or find it in the URL when viewi
170
174
  |------|-------------|
171
175
  | `list_listings` | List all listings (paginated) |
172
176
  | `get_listing` | Get full listing details with custom fields |
173
- | `create_listing` | Create a new listing with custom field values |
174
- | `update_listing` | Update a listing |
177
+ | `create_listing` | Create a new listing with custom field values (and link organizers) |
178
+ | `update_listing` | Update a listing (including its linked organizers) |
175
179
  | `delete_listing` | Delete a listing |
176
180
  | `check_listing_exists` | Check if a URL already exists |
177
181
  | `bulk_create_listings` | Create up to 100 listings at once |
178
182
 
183
+ Listings can be linked to one or more organizers by passing an `organizers` array of organizer IDs to `create_listing` / `update_listing` (use `list_organizers` to find the IDs). On update, the array replaces the current set.
184
+
179
185
  ### Articles
180
186
 
181
187
  | Tool | Description |
@@ -198,6 +204,20 @@ Ask Claude: *"List my Directify directories"* - or find it in the URL when viewi
198
204
  | `delete_page` | Delete a page |
199
205
  | `toggle_page` | Toggle published/unpublished status |
200
206
 
207
+ ### Organizers
208
+
209
+ Organizers represent entities like agencies, studios, or event hosts that can be linked to multiple listings.
210
+
211
+ | Tool | Description |
212
+ |------|-------------|
213
+ | `list_organizers` | List all organizers (with their linked listings) |
214
+ | `get_organizer` | Get organizer details (with linked listings) |
215
+ | `create_organizer` | Create a new organizer |
216
+ | `update_organizer` | Update an organizer |
217
+ | `delete_organizer` | Delete an organizer |
218
+
219
+ To link an organizer to a listing, pass the organizer's ID in the `organizers` array of `create_listing` / `update_listing` (see Listings above).
220
+
201
221
  ## Example Conversations
202
222
 
203
223
  ### Create a listing
@@ -224,6 +244,18 @@ Claude will use `create_article` with markdown content.
224
244
 
225
245
  Claude will use `list_listings`, then `update_listing` for each one.
226
246
 
247
+ ### Link listings to an organizer
248
+
249
+ > **You:** Create an organizer "Acme Events", then add these 3 festival listings and link them all to it.
250
+
251
+ Claude will use `create_organizer`, then `create_listing` for each festival with the new organizer's ID in the `organizers` array.
252
+
253
+ ### Define a custom field
254
+
255
+ > **You:** Add a "Price Range" dropdown field with options $, $$, and $$$, and make it filterable.
256
+
257
+ Claude will use `create_custom_field` with `type: "select"`, the options, and `filterable: true` — then you can set the value per listing via `create_listing` / `update_listing`.
258
+
227
259
  ### Create programmatic SEO pages
228
260
 
229
261
  > **You:** Create comparison pages for "NYC vs Chicago pizza", "NYC vs LA tacos", and "NYC vs Boston seafood" with SEO titles and descriptions.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directify-mcp",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
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
@@ -242,6 +242,116 @@ export const listCustomFields = {
242
242
  },
243
243
  };
244
244
 
245
+ const CUSTOM_FIELD_TYPES = [
246
+ 'text', 'number', 'date', 'file_upload', 'url', 'email', 'rich_editor',
247
+ 'markdown', 'textarea', 'checkbox', 'rating', 'select', 'list',
248
+ 'multi_select', 'button', 'javascript', 'html',
249
+ ];
250
+
251
+ export const getCustomField = {
252
+ name: 'get_custom_field',
253
+ description: 'Get the definition of a specific custom field.',
254
+ inputSchema: {
255
+ type: 'object',
256
+ properties: {
257
+ directory_id: { type: 'string', description: 'Directory ID' },
258
+ custom_field_id: { type: 'string', description: 'Custom field ID' },
259
+ },
260
+ required: ['custom_field_id'],
261
+ },
262
+ handler: async ({ directory_id, custom_field_id }) => {
263
+ const dir = resolveDirectory(directory_id);
264
+ const data = await api.get(`/directories/${dir}/custom-fields/${custom_field_id}`);
265
+ return data.data || data;
266
+ },
267
+ };
268
+
269
+ export const createCustomField = {
270
+ name: 'create_custom_field',
271
+ description:
272
+ 'Create a new custom field definition for a directory. Field values are then set on listings by passing the field name as a key (see create_listing / update_listing).',
273
+ inputSchema: {
274
+ type: 'object',
275
+ properties: {
276
+ directory_id: { type: 'string', description: 'Directory ID' },
277
+ label: { type: 'string', description: 'Human-readable field label (shown to users)' },
278
+ type: { type: 'string', enum: CUSTOM_FIELD_TYPES, description: 'Field type' },
279
+ name: { type: 'string', description: 'Machine name/key used when setting values (auto-derived from label, snake_cased, if omitted)' },
280
+ fieldable_type: { type: 'string', enum: ['listing', 'organizer', 'article'], description: 'What the field attaches to (default: listing)' },
281
+ placeholder: { type: 'string', description: 'Input placeholder text' },
282
+ description: { type: 'string', description: 'Help text shown under the field' },
283
+ default_value: { type: 'string', description: 'Default value' },
284
+ value_prefix: { type: 'string', description: 'Prefix shown before the value (e.g. "$")' },
285
+ value_suffix: { type: 'string', description: 'Suffix shown after the value (e.g. "/mo")' },
286
+ options: { type: 'array', items: { type: 'string' }, description: 'Options for select / multi_select / list field types' },
287
+ is_required: { type: 'boolean', description: 'Whether the field is required' },
288
+ is_visible: { type: 'boolean', description: 'Whether the field is shown on the listing page (default: true)' },
289
+ show_on_card: { type: 'boolean', description: 'Show the value on listing cards' },
290
+ show_on_public_submission: { type: 'boolean', description: 'Show the field on the public submission form' },
291
+ filterable: { type: 'boolean', description: 'Allow filtering listings by this field' },
292
+ order: { type: 'number', description: 'Sort order among fields' },
293
+ },
294
+ required: ['label', 'type'],
295
+ },
296
+ handler: async ({ directory_id, ...body }) => {
297
+ const dir = resolveDirectory(directory_id);
298
+ const data = await api.post(`/directories/${dir}/custom-fields`, body);
299
+ return data.data || data;
300
+ },
301
+ };
302
+
303
+ export const updateCustomField = {
304
+ name: 'update_custom_field',
305
+ description: 'Update an existing custom field definition. Only pass fields you want to change.',
306
+ inputSchema: {
307
+ type: 'object',
308
+ properties: {
309
+ directory_id: { type: 'string', description: 'Directory ID' },
310
+ custom_field_id: { type: 'string', description: 'Custom field ID to update' },
311
+ label: { type: 'string', description: 'Human-readable field label' },
312
+ type: { type: 'string', enum: CUSTOM_FIELD_TYPES, description: 'Field type' },
313
+ name: { type: 'string', description: 'Machine name/key used when setting values' },
314
+ fieldable_type: { type: 'string', enum: ['listing', 'organizer', 'article'], description: 'What the field attaches to' },
315
+ placeholder: { type: 'string', description: 'Input placeholder text' },
316
+ description: { type: 'string', description: 'Help text shown under the field' },
317
+ default_value: { type: 'string', description: 'Default value' },
318
+ value_prefix: { type: 'string', description: 'Prefix shown before the value' },
319
+ value_suffix: { type: 'string', description: 'Suffix shown after the value' },
320
+ options: { type: 'array', items: { type: 'string' }, description: 'Options for select / multi_select / list field types' },
321
+ is_required: { type: 'boolean', description: 'Whether the field is required' },
322
+ is_visible: { type: 'boolean', description: 'Whether the field is shown on the listing page' },
323
+ show_on_card: { type: 'boolean', description: 'Show the value on listing cards' },
324
+ show_on_public_submission: { type: 'boolean', description: 'Show the field on the public submission form' },
325
+ filterable: { type: 'boolean', description: 'Allow filtering listings by this field' },
326
+ order: { type: 'number', description: 'Sort order among fields' },
327
+ },
328
+ required: ['custom_field_id'],
329
+ },
330
+ handler: async ({ directory_id, custom_field_id, ...body }) => {
331
+ const dir = resolveDirectory(directory_id);
332
+ const data = await api.put(`/directories/${dir}/custom-fields/${custom_field_id}`, body);
333
+ return data.data || data;
334
+ },
335
+ };
336
+
337
+ export const deleteCustomField = {
338
+ name: 'delete_custom_field',
339
+ description: 'Delete a custom field definition from a directory. This also removes the field\'s values from all listings.',
340
+ inputSchema: {
341
+ type: 'object',
342
+ properties: {
343
+ directory_id: { type: 'string', description: 'Directory ID' },
344
+ custom_field_id: { type: 'string', description: 'Custom field ID to delete' },
345
+ },
346
+ required: ['custom_field_id'],
347
+ },
348
+ handler: async ({ directory_id, custom_field_id }) => {
349
+ const dir = resolveDirectory(directory_id);
350
+ await api.delete(`/directories/${dir}/custom-fields/${custom_field_id}`);
351
+ return { success: true, message: `Custom field ${custom_field_id} deleted.` };
352
+ },
353
+ };
354
+
245
355
  // ─── Listings ───
246
356
 
247
357
  export const listListings = {
@@ -282,7 +392,7 @@ export const getListing = {
282
392
  export const createListing = {
283
393
  name: 'create_listing',
284
394
  description:
285
- 'Create a new listing in a directory. You can include custom field values by using the field name as a key (use list_custom_fields to see available fields).',
395
+ 'Create a new listing in a directory. You can include custom field values by using the field name as a key (use list_custom_fields to see available fields). Link the listing to organizers by passing their IDs (use list_organizers to find them).',
286
396
  inputSchema: {
287
397
  type: 'object',
288
398
  properties: {
@@ -309,6 +419,11 @@ export const createListing = {
309
419
  items: { type: 'number' },
310
420
  description: 'Array of tag IDs',
311
421
  },
422
+ organizers: {
423
+ type: 'array',
424
+ items: { type: 'number' },
425
+ description: 'Array of organizer IDs to link this listing to (must belong to the same directory; use list_organizers to find them)',
426
+ },
312
427
  is_active: { type: 'boolean', description: 'Active status (default: true)' },
313
428
  is_featured: { type: 'boolean', description: 'Featured status' },
314
429
  custom_fields: {
@@ -329,7 +444,7 @@ export const createListing = {
329
444
 
330
445
  export const updateListing = {
331
446
  name: 'update_listing',
332
- description: 'Update an existing listing. Only pass fields you want to change.',
447
+ description: "Update an existing listing. Only pass fields you want to change. Pass organizers to replace the listing's linked organizers (omit it to leave them untouched).",
333
448
  inputSchema: {
334
449
  type: 'object',
335
450
  properties: {
@@ -357,6 +472,11 @@ export const updateListing = {
357
472
  items: { type: 'number' },
358
473
  description: 'Array of tag IDs',
359
474
  },
475
+ organizers: {
476
+ type: 'array',
477
+ items: { type: 'number' },
478
+ description: 'Array of organizer IDs to link this listing to. Replaces the current set; pass [] to unlink all. Out-of-directory IDs are ignored.',
479
+ },
360
480
  is_active: { type: 'boolean', description: 'Active status' },
361
481
  is_featured: { type: 'boolean', description: 'Featured status' },
362
482
  custom_fields: {
@@ -436,6 +556,7 @@ export const bulkCreateListings = {
436
556
  description: { type: 'string' },
437
557
  categories: { type: 'array', items: { type: 'number' } },
438
558
  tags: { type: 'array', items: { type: 'number' } },
559
+ organizers: { type: 'array', items: { type: 'number' } },
439
560
  },
440
561
  required: ['name'],
441
562
  additionalProperties: true,
@@ -856,6 +977,10 @@ export const allTools = [
856
977
  updateTag,
857
978
  deleteTag,
858
979
  listCustomFields,
980
+ getCustomField,
981
+ createCustomField,
982
+ updateCustomField,
983
+ deleteCustomField,
859
984
  listListings,
860
985
  getListing,
861
986
  createListing,