propstack-mcp-server 1.0.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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +321 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +55 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/propstack-client.d.ts +24 -0
  8. package/dist/propstack-client.d.ts.map +1 -0
  9. package/dist/propstack-client.js +99 -0
  10. package/dist/propstack-client.js.map +1 -0
  11. package/dist/tools/activities.d.ts +4 -0
  12. package/dist/tools/activities.d.ts.map +1 -0
  13. package/dist/tools/activities.js +177 -0
  14. package/dist/tools/activities.js.map +1 -0
  15. package/dist/tools/admin.d.ts +4 -0
  16. package/dist/tools/admin.d.ts.map +1 -0
  17. package/dist/tools/admin.js +148 -0
  18. package/dist/tools/admin.js.map +1 -0
  19. package/dist/tools/composites.d.ts +4 -0
  20. package/dist/tools/composites.d.ts.map +1 -0
  21. package/dist/tools/composites.js +807 -0
  22. package/dist/tools/composites.js.map +1 -0
  23. package/dist/tools/contacts.d.ts +4 -0
  24. package/dist/tools/contacts.d.ts.map +1 -0
  25. package/dist/tools/contacts.js +386 -0
  26. package/dist/tools/contacts.js.map +1 -0
  27. package/dist/tools/deals.d.ts +4 -0
  28. package/dist/tools/deals.d.ts.map +1 -0
  29. package/dist/tools/deals.js +230 -0
  30. package/dist/tools/deals.js.map +1 -0
  31. package/dist/tools/documents.d.ts +4 -0
  32. package/dist/tools/documents.d.ts.map +1 -0
  33. package/dist/tools/documents.js +109 -0
  34. package/dist/tools/documents.js.map +1 -0
  35. package/dist/tools/emails.d.ts +4 -0
  36. package/dist/tools/emails.d.ts.map +1 -0
  37. package/dist/tools/emails.js +111 -0
  38. package/dist/tools/emails.js.map +1 -0
  39. package/dist/tools/helpers.d.ts +39 -0
  40. package/dist/tools/helpers.d.ts.map +1 -0
  41. package/dist/tools/helpers.js +160 -0
  42. package/dist/tools/helpers.js.map +1 -0
  43. package/dist/tools/lookups.d.ts +4 -0
  44. package/dist/tools/lookups.d.ts.map +1 -0
  45. package/dist/tools/lookups.js +333 -0
  46. package/dist/tools/lookups.js.map +1 -0
  47. package/dist/tools/projects.d.ts +4 -0
  48. package/dist/tools/projects.d.ts.map +1 -0
  49. package/dist/tools/projects.js +104 -0
  50. package/dist/tools/projects.js.map +1 -0
  51. package/dist/tools/properties.d.ts +4 -0
  52. package/dist/tools/properties.d.ts.map +1 -0
  53. package/dist/tools/properties.js +397 -0
  54. package/dist/tools/properties.js.map +1 -0
  55. package/dist/tools/relationships.d.ts +4 -0
  56. package/dist/tools/relationships.d.ts.map +1 -0
  57. package/dist/tools/relationships.js +55 -0
  58. package/dist/tools/relationships.js.map +1 -0
  59. package/dist/tools/search-profiles.d.ts +4 -0
  60. package/dist/tools/search-profiles.d.ts.map +1 -0
  61. package/dist/tools/search-profiles.js +345 -0
  62. package/dist/tools/search-profiles.js.map +1 -0
  63. package/dist/tools/tasks.d.ts +4 -0
  64. package/dist/tools/tasks.d.ts.map +1 -0
  65. package/dist/tools/tasks.js +251 -0
  66. package/dist/tools/tasks.js.map +1 -0
  67. package/dist/types/propstack.d.ts +444 -0
  68. package/dist/types/propstack.d.ts.map +1 -0
  69. package/dist/types/propstack.js +3 -0
  70. package/dist/types/propstack.js.map +1 -0
  71. package/package.json +54 -0
@@ -0,0 +1,160 @@
1
+ import { PropstackError } from "../propstack-client.js";
2
+ export function textResult(text) {
3
+ return { content: [{ type: "text", text }] };
4
+ }
5
+ /**
6
+ * Parse 422 validation error body into human-readable field errors.
7
+ */
8
+ function parse422(detail) {
9
+ try {
10
+ const parsed = JSON.parse(detail);
11
+ // Propstack returns { errors: { field: ["msg", ...] } } or { error: "msg" }
12
+ if (parsed.errors && typeof parsed.errors === "object") {
13
+ const lines = [];
14
+ for (const [field, msgs] of Object.entries(parsed.errors)) {
15
+ const msgList = Array.isArray(msgs) ? msgs.join(", ") : String(msgs);
16
+ lines.push(` ${field}: ${msgList}`);
17
+ }
18
+ if (lines.length > 0)
19
+ return `Validation failed:\n${lines.join("\n")}`;
20
+ }
21
+ if (parsed.error)
22
+ return `Validation error: ${parsed.error}`;
23
+ }
24
+ catch {
25
+ // Not JSON, return as-is
26
+ }
27
+ return `Validation error: ${detail}`;
28
+ }
29
+ /**
30
+ * Extract a resource ID from the API path for better 404 messages.
31
+ * e.g. "/contacts/123" → "123", "/units/456" → "456"
32
+ */
33
+ function extractIdFromPath(path) {
34
+ const match = /\/(\d+)(?:\/|$)/.exec(path);
35
+ return match?.[1] ?? null;
36
+ }
37
+ /**
38
+ * Format any error into a user-friendly string.
39
+ */
40
+ export function formatError(err) {
41
+ if (err instanceof PropstackError) {
42
+ const id = extractIdFromPath(err.path);
43
+ switch (err.status) {
44
+ case 401:
45
+ return "Invalid API key. Check your PROPSTACK_API_KEY. Manage keys at crm.propstack.de/app/admin/api_keys";
46
+ case 403:
47
+ return "Insufficient permissions. Check API key permissions in Propstack admin.";
48
+ case 404:
49
+ return id
50
+ ? `Not found. The resource with ID ${id} does not exist (${err.path}).`
51
+ : `Not found (${err.path}).`;
52
+ case 422:
53
+ return parse422(err.detail);
54
+ case 429:
55
+ return "Rate limited by Propstack API. Please try again in a moment.";
56
+ default:
57
+ return `Propstack API error ${err.status}: ${err.detail}`;
58
+ }
59
+ }
60
+ if (err instanceof TypeError && (err.message.includes("fetch") || err.message.includes("ECONNREFUSED") || err.message.includes("ENOTFOUND"))) {
61
+ return `Network error: could not reach Propstack API. Check your internet connection. (${err.message})`;
62
+ }
63
+ if (err instanceof Error)
64
+ return err.message;
65
+ return String(err);
66
+ }
67
+ export function errorResult(entity, err) {
68
+ if (err instanceof PropstackError) {
69
+ const id = extractIdFromPath(err.path);
70
+ if (err.status === 401) {
71
+ return textResult("Invalid API key. Check your PROPSTACK_API_KEY. Manage keys at crm.propstack.de/app/admin/api_keys");
72
+ }
73
+ if (err.status === 403) {
74
+ return textResult("Insufficient permissions. Check API key permissions in Propstack admin.");
75
+ }
76
+ if (err.status === 404) {
77
+ return id
78
+ ? textResult(`${entity} not found. No ${entity.toLowerCase()} with ID ${id} exists.`)
79
+ : textResult(`${entity} not found.`);
80
+ }
81
+ if (err.status === 422) {
82
+ return textResult(parse422(err.detail));
83
+ }
84
+ if (err.status === 429) {
85
+ return textResult("Rate limited by Propstack API. Please try again in a moment.");
86
+ }
87
+ return textResult(`Propstack API error ${err.status}: ${err.detail}`);
88
+ }
89
+ const msg = formatError(err);
90
+ return { content: [{ type: "text", text: `Error: ${msg}` }], isError: true };
91
+ }
92
+ /**
93
+ * Propstack API with new=1/expand=1 returns nested objects like { value: X } or
94
+ * { value: X, pretty_value: Y }. Unwrap to the underlying primitive for display.
95
+ */
96
+ export function unwrapPropstackValue(val) {
97
+ if (val === null || val === undefined)
98
+ return val;
99
+ if (typeof val === "object" && val !== null) {
100
+ const o = val;
101
+ if ("pretty_value" in o && o.pretty_value != null && o.pretty_value !== "") {
102
+ return unwrapPropstackValue(o.pretty_value);
103
+ }
104
+ if ("value" in o) {
105
+ return unwrapPropstackValue(o.value);
106
+ }
107
+ }
108
+ return val;
109
+ }
110
+ export function fmt(value, fallback = "none") {
111
+ const v = unwrapPropstackValue(value);
112
+ if (v === null || v === undefined || v === "")
113
+ return fallback;
114
+ if (typeof v === "object")
115
+ return fallback;
116
+ return String(v);
117
+ }
118
+ /** Unwrap and coerce to number for price/area formatting. */
119
+ export function unwrapNumber(val) {
120
+ const v = unwrapPropstackValue(val);
121
+ if (typeof v === "number" && !Number.isNaN(v))
122
+ return v;
123
+ if (typeof v === "string") {
124
+ const n = parseFloat(v);
125
+ return Number.isNaN(n) ? null : n;
126
+ }
127
+ return null;
128
+ }
129
+ export function fmtPrice(value) {
130
+ const n = unwrapNumber(value);
131
+ if (n === null)
132
+ return "none";
133
+ return n.toLocaleString("de-DE", { style: "currency", currency: "EUR", maximumFractionDigits: 0 });
134
+ }
135
+ export function fmtArea(value, unit = "m²") {
136
+ const n = unwrapNumber(value);
137
+ if (n === null)
138
+ return "none";
139
+ return `${n} ${unit}`;
140
+ }
141
+ export function fmtNested(obj, key, fallback = "none") {
142
+ if (!obj || typeof obj !== "object")
143
+ return fallback;
144
+ const val = obj[key];
145
+ return fmt(val, fallback);
146
+ }
147
+ /**
148
+ * Remove keys with undefined values from an object so we don't send
149
+ * nulls to the API when optional fields are omitted.
150
+ */
151
+ export function stripUndefined(obj) {
152
+ const result = {};
153
+ for (const [key, value] of Object.entries(obj)) {
154
+ if (value !== undefined) {
155
+ result[key] = value;
156
+ }
157
+ }
158
+ return result;
159
+ }
160
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/tools/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { PropstackClient } from "../propstack-client.js";
3
+ export declare function registerLookupTools(server: McpServer, client: PropstackClient): void;
4
+ //# sourceMappingURL=lookups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lookups.d.ts","sourceRoot":"","sources":["../../src/tools/lookups.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAmD9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAsZpF"}
@@ -0,0 +1,333 @@
1
+ import { z } from "zod";
2
+ import { textResult, errorResult, fmt } from "./helpers.js";
3
+ // ── Response formatting ──────────────────────────────────────────────
4
+ function formatPipeline(p) {
5
+ const lines = [
6
+ `**${fmt(p.name, "Untitled")}** (ID: ${p.id})`,
7
+ ];
8
+ if (p.broker_ids?.length) {
9
+ lines.push(`Brokers: ${p.broker_ids.join(", ")}`);
10
+ }
11
+ if (p.deal_stages?.length) {
12
+ lines.push("Stages:");
13
+ for (const s of p.deal_stages) {
14
+ const chance = s.chance !== null && s.chance !== undefined ? ` (${s.chance}%)` : "";
15
+ lines.push(` ${s.position ?? "?"}) **${fmt(s.name)}** (ID: ${s.id})${chance}`);
16
+ }
17
+ }
18
+ return lines.join("\n");
19
+ }
20
+ function formatBroker(b) {
21
+ const lines = [
22
+ `**${fmt(b.name)}** (ID: ${b.id})`,
23
+ `Email: ${fmt(b.email)}`,
24
+ b.phone ? `Phone: ${b.phone}` : null,
25
+ b.position ? `Position: ${b.position}` : null,
26
+ b.team_id ? `Team ID: ${b.team_id}` : null,
27
+ b.department_ids?.length ? `Departments: ${b.department_ids.join(", ")}` : null,
28
+ ];
29
+ return lines.filter(Boolean).join("\n");
30
+ }
31
+ // ── Tool registration ────────────────────────────────────────────────
32
+ export function registerLookupTools(server, client) {
33
+ // ── list_pipelines ──────────────────────────────────────────────
34
+ server.tool("list_pipelines", `List all deal pipelines and their stages in Propstack.
35
+
36
+ Returns each pipeline with its ordered stages, including stage IDs,
37
+ names, positions, colors, and chance percentages.
38
+
39
+ You NEED stage IDs from this tool to create or move deals. Call this
40
+ before using create_deal or update_deal if you don't know the stage IDs.
41
+
42
+ Typical pipelines: Sales (Verkauf), Acquisition (Akquise), Rental (Vermietung).
43
+ Typical stages: Anfrage → Besichtigung → Reserviert → Notartermin → Verkauft.`, {}, async () => {
44
+ try {
45
+ const pipelines = await client.get("/deal_pipelines");
46
+ if (!pipelines || pipelines.length === 0) {
47
+ return textResult("No deal pipelines configured.");
48
+ }
49
+ const formatted = pipelines.map(formatPipeline).join("\n\n---\n\n");
50
+ return textResult(`Deal pipelines:\n\n${formatted}`);
51
+ }
52
+ catch (err) {
53
+ return errorResult("Pipeline", err);
54
+ }
55
+ });
56
+ // ── get_pipeline ────────────────────────────────────────────────
57
+ server.tool("get_pipeline", `Get a single deal pipeline by ID with its stages.
58
+
59
+ Returns the pipeline with all stages in order. Use this when you
60
+ already know which pipeline you need and want its stage details.`, {
61
+ id: z.number()
62
+ .describe("Pipeline ID"),
63
+ }, async (args) => {
64
+ try {
65
+ const pipeline = await client.get(`/deal_pipelines/${args.id}`);
66
+ return textResult(formatPipeline(pipeline));
67
+ }
68
+ catch (err) {
69
+ return errorResult("Pipeline", err);
70
+ }
71
+ });
72
+ // ── list_tags / list_groups ──────────────────────────────────────
73
+ server.tool("list_tags", `List all tags/labels (Merkmale/Gruppen) in Propstack.
74
+
75
+ Use these IDs to filter contacts (search_contacts group param) or assign
76
+ tags (create_contact/update_contact group_ids). Filter by entity to get
77
+ tags for contacts, properties, or activities.
78
+
79
+ Entity: for_clients (default), for_properties, for_activities.
80
+
81
+ Returns flat list: **Name** (ID: 123). Use super_groups param to optionally
82
+ try hierarchical view via /super_groups.`, {
83
+ entity: z.enum(["for_clients", "for_properties", "for_activities"]).optional()
84
+ .describe("Filter by entity type (default: for_clients)"),
85
+ super_groups: z.boolean().optional()
86
+ .describe("If true, fetch hierarchical structure from /super_groups (Obermerkmale with children)"),
87
+ }, async (args) => {
88
+ try {
89
+ const params = {};
90
+ if (args.entity)
91
+ params["entity"] = args.entity;
92
+ if (args.super_groups) {
93
+ const res = await client.get("/super_groups", { params: { ...params, include: "groups" } });
94
+ const sgs = Array.isArray(res) ? res : res?.data ?? [];
95
+ if (!sgs.length)
96
+ return textResult("No super groups found.");
97
+ const lines = [];
98
+ for (const sg of sgs) {
99
+ lines.push(`**${fmt(sg.name, "Ungrouped")}** (ID: ${sg.id})`);
100
+ if (sg.groups?.length) {
101
+ for (const g of sg.groups) {
102
+ lines.push(` • ${fmt(g.name)} (ID: ${g.id})`);
103
+ }
104
+ }
105
+ }
106
+ return textResult(`Tags (hierarchical):\n\n${lines.join("\n")}`);
107
+ }
108
+ const raw = await client.get("/groups", { params });
109
+ const items = Array.isArray(raw) ? raw : [];
110
+ if (!items.length) {
111
+ return textResult("No tags found.");
112
+ }
113
+ // API returns flat [{id, name, super_group_id, public_name}] or hierarchical [{id, name, groups: []}]
114
+ const flatItems = [];
115
+ for (const item of items) {
116
+ const sg = item;
117
+ if (sg.groups?.length) {
118
+ for (const g of sg.groups)
119
+ flatItems.push({ id: g.id, name: g.name, super_group_id: g.super_group_id });
120
+ }
121
+ else {
122
+ flatItems.push({
123
+ id: item.id,
124
+ name: item.name,
125
+ super_group_id: item.super_group_id,
126
+ });
127
+ }
128
+ }
129
+ const lines = flatItems.map((g) => `- **${fmt(g.name)}** (ID: ${g.id})`);
130
+ return textResult(`Tags/Groups (Merkmale) — use these IDs for search_contacts group filter:\n\n${lines.join("\n")}`);
131
+ }
132
+ catch (err) {
133
+ return errorResult("Tag", err);
134
+ }
135
+ });
136
+ // ── create_tag ──────────────────────────────────────────────────
137
+ server.tool("create_tag", `Create a new tag/label (Merkmal) in Propstack.
138
+
139
+ Tags are used to categorize contacts, properties, and activities.
140
+ Optionally assign to a parent super-group (Obermerkmal) for hierarchy.
141
+
142
+ Examples: "Penthouse-Käufer", "VIP", "Kapitalanleger", "Erstbezug".`, {
143
+ name: z.string()
144
+ .describe("Tag name"),
145
+ entity: z.enum(["for_clients", "for_properties", "for_activities"])
146
+ .describe("Which entity type this tag applies to"),
147
+ super_group_id: z.number().optional()
148
+ .describe("Parent super-group ID (Obermerkmal) for hierarchy"),
149
+ }, async (args) => {
150
+ try {
151
+ const tag = await client.post("/groups", { body: args });
152
+ return textResult(`Tag created: **${fmt(tag.name)}** (ID: ${tag.id})` +
153
+ (tag.super_group_id ? ` — parent group: ${tag.super_group_id}` : ""));
154
+ }
155
+ catch (err) {
156
+ return errorResult("Tag", err);
157
+ }
158
+ });
159
+ // ── list_custom_fields ──────────────────────────────────────────
160
+ server.tool("list_custom_fields", `List custom field definitions for an entity type in Propstack.
161
+
162
+ IMPORTANT: Call this tool to discover what custom fields exist before
163
+ reading or writing custom field values on contacts, properties, etc.
164
+
165
+ Returns field groups, each containing field definitions with:
166
+ - name: The API key to use (e.g. "cf_budget_range")
167
+ - pretty_name: Human-readable label (e.g. "Budget Range")
168
+ - type: Field type (String, Dropdown, Number, Date, etc.)
169
+ - options: Available values for Dropdown fields
170
+
171
+ To READ custom fields: use expand=true on search or get tools.
172
+ To WRITE custom fields: use partial_custom_fields: {"cf_field_name": "value"}.
173
+ To FILTER by custom fields: add cf_fieldname=value as a search param.`, {
174
+ entity: z.enum(["for_clients", "for_properties", "for_projects", "for_brokers", "for_tasks", "for_deals"])
175
+ .describe("Entity type to get custom fields for"),
176
+ }, async (args) => {
177
+ try {
178
+ const groups = await client.get("/custom_field_groups", { params: { entity: args.entity } });
179
+ if (!groups || groups.length === 0) {
180
+ return textResult(`No custom fields configured for ${args.entity}.`);
181
+ }
182
+ const lines = [];
183
+ for (const g of groups) {
184
+ lines.push(`**${fmt(g.name, "Default")}** (Group ID: ${g.id})`);
185
+ if (g.fields?.length) {
186
+ for (const f of g.fields) {
187
+ let desc = ` • \`${f.name}\` — ${fmt(f.pretty_name)} (${fmt(f.type)})`;
188
+ if (f.options?.length) {
189
+ desc += `\n Options: ${f.options.join(", ")}`;
190
+ }
191
+ lines.push(desc);
192
+ }
193
+ }
194
+ }
195
+ return textResult(`Custom fields for ${args.entity}:\n\n${lines.join("\n")}`);
196
+ }
197
+ catch (err) {
198
+ return errorResult("Custom field", err);
199
+ }
200
+ });
201
+ // ── list_users ──────────────────────────────────────────────────
202
+ server.tool("list_users", `List all brokers/agents (Nutzer) in the Propstack account.
203
+
204
+ Returns team members with their IDs, names, email, phone, position,
205
+ team, and department assignments.
206
+
207
+ You need broker IDs for:
208
+ - Assigning contacts or properties to a broker
209
+ - Filtering by broker in search tools
210
+ - Setting the sender for emails (send_email broker_id)
211
+ - Assigning tasks and events`, {}, async () => {
212
+ try {
213
+ const brokers = await client.get("/brokers");
214
+ if (!brokers || brokers.length === 0) {
215
+ return textResult("No brokers/users found.");
216
+ }
217
+ const formatted = brokers.map(formatBroker).join("\n\n---\n\n");
218
+ return textResult(`Brokers/Users:\n\n${formatted}`);
219
+ }
220
+ catch (err) {
221
+ return errorResult("Broker", err);
222
+ }
223
+ });
224
+ // ── list_teams ──────────────────────────────────────────────────
225
+ server.tool("list_teams", `List all teams/departments in Propstack.
226
+
227
+ Returns teams with their broker member assignments. Use for team-level
228
+ filtering and to understand the organizational structure.`, {}, async () => {
229
+ try {
230
+ const teams = await client.get("/teams");
231
+ if (!teams || teams.length === 0) {
232
+ return textResult("No teams configured.");
233
+ }
234
+ const lines = teams.map((t) => {
235
+ const members = t.broker_ids?.length ? `Members: ${t.broker_ids.join(", ")}` : "No members";
236
+ return `**${fmt(t.name)}** (ID: ${t.id}) — ${members}`;
237
+ });
238
+ return textResult(`Teams:\n\n${lines.join("\n")}`);
239
+ }
240
+ catch (err) {
241
+ return errorResult("Team", err);
242
+ }
243
+ });
244
+ // ── list_activity_types ─────────────────────────────────────────
245
+ server.tool("list_activity_types", `List all activity/task types in Propstack.
246
+
247
+ These are templates for creating notes, todos (reminders), events, and messages.
248
+ Each has an id, name, and category. Use these IDs when creating tasks:
249
+ - category "for_notes" → note_type_id in create_task
250
+ - category "for_reminders" → todo_type_id (when is_reminder: true)
251
+ - category "for_events" → event_type_id (when is_event: true)
252
+ - category "message" → snippet_id for email templates
253
+
254
+ Categories map to search_activities filter: for_notes→note, for_reminders→reminder,
255
+ for_events→event, message→message.`, {
256
+ category: z.enum(["message", "for_notes", "for_reminders", "for_events"]).optional()
257
+ .describe("Filter by category (message, for_notes, for_reminders, for_events)"),
258
+ }, async (args) => {
259
+ try {
260
+ const res = await client.get("/activity_types", args.category ? { params: { category: args.category } } : undefined);
261
+ let types = Array.isArray(res) ? res : res?.data ?? [];
262
+ if (args.category) {
263
+ types = types.filter((t) => fmt(t.category) === args.category);
264
+ }
265
+ if (!types.length) {
266
+ return textResult(args.category
267
+ ? `No activity types in category "${args.category}".`
268
+ : "No activity types configured.");
269
+ }
270
+ const lines = types.map((t) => {
271
+ const cat = fmt(t.category, "");
272
+ return `- **${fmt(t.name)}** (ID: ${t.id}) — ${cat}`;
273
+ });
274
+ return textResult(`Activity types${args.category ? ` (${args.category})` : ""}:\n\n${lines.join("\n")}`);
275
+ }
276
+ catch (err) {
277
+ return errorResult("Activity type", err);
278
+ }
279
+ });
280
+ // ── list_contact_statuses ──────────────────────────────────────
281
+ server.tool("list_contact_statuses", `List contact statuses (Kontaktstatus) in Propstack.
282
+
283
+ Use these IDs for search_contacts (status param) and create_contact/update_contact
284
+ (client_status_id). E.g. "Lead", "Kunde", "Archiviert".`, {}, async () => {
285
+ try {
286
+ const raw = await client.get("/contact_statuses");
287
+ const items = Array.isArray(raw) ? raw : raw?.data ?? [];
288
+ if (!items.length)
289
+ return textResult("No contact statuses found.");
290
+ const lines = items.map((s) => `- **${fmt(s.name)}** (ID: ${s.id})`);
291
+ return textResult(`Contact statuses:\n\n${lines.join("\n")}`);
292
+ }
293
+ catch (err) {
294
+ return errorResult("Contact status", err);
295
+ }
296
+ });
297
+ // ── list_reservation_reasons ────────────────────────────────────
298
+ server.tool("list_reservation_reasons", `List deal cancellation reasons (Reservierungsgründe/Absagegründe).
299
+
300
+ Use when creating deal cancellations (create_task with reservation_reason_id)
301
+ or filtering lost deals (search_deals reservation_reason_ids).`, {}, async () => {
302
+ try {
303
+ const raw = await client.get("/reservation_reasons");
304
+ const items = Array.isArray(raw) ? raw : raw?.data ?? [];
305
+ if (!items.length)
306
+ return textResult("No reservation reasons found.");
307
+ const lines = items.map((r) => `- **${fmt(r.name)}** (ID: ${r.id})`);
308
+ return textResult(`Reservation/cancellation reasons:\n\n${lines.join("\n")}`);
309
+ }
310
+ catch (err) {
311
+ return errorResult("Reservation reason", err);
312
+ }
313
+ });
314
+ // ── list_locations ──────────────────────────────────────────────
315
+ server.tool("list_locations", `List geographic areas/districts (Geolagen) in Propstack.
316
+
317
+ Returns location IDs and names used for property and search profile
318
+ location matching. Use location IDs when creating search profiles
319
+ or filtering properties by area.`, {}, async () => {
320
+ try {
321
+ const locations = await client.get("/locations");
322
+ if (!locations || locations.length === 0) {
323
+ return textResult("No locations configured.");
324
+ }
325
+ const lines = locations.map((l) => `- **${fmt(l.name)}** (ID: ${l.id})`);
326
+ return textResult(`Locations:\n\n${lines.join("\n")}`);
327
+ }
328
+ catch (err) {
329
+ return errorResult("Location", err);
330
+ }
331
+ });
332
+ }
333
+ //# sourceMappingURL=lookups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lookups.js","sourceRoot":"","sources":["../../src/tools/lookups.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { PropstackClient } from "../propstack-client.js";
3
+ export declare function registerProjectTools(server: McpServer, client: PropstackClient): void;
4
+ //# sourceMappingURL=projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.d.ts","sourceRoot":"","sources":["../../src/tools/projects.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAgD9D,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI,CAkFrF"}
@@ -0,0 +1,104 @@
1
+ import { z } from "zod";
2
+ import { textResult, errorResult, fmt } from "./helpers.js";
3
+ // ── Response formatting ──────────────────────────────────────────────
4
+ function formatProject(p) {
5
+ const title = fmt(p.title ?? p.name, "Untitled");
6
+ const addr = [p.street, p.house_number].filter(Boolean).join(" ");
7
+ const cityLine = [p.zip_code, p.city].filter(Boolean).join(" ");
8
+ const fullAddress = [addr, cityLine, p.country].filter(Boolean).join(", ");
9
+ const lines = [
10
+ `**${title}** (ID: ${p.id})`,
11
+ `Status: ${fmt(p.status)}`,
12
+ `Address: ${fullAddress || "none"}`,
13
+ `Broker: ${fmt(p.broker_id, "unassigned")}`,
14
+ ];
15
+ // Units summary
16
+ if (p.units && p.units.length > 0) {
17
+ lines.push(`Units: ${p.units.length}`);
18
+ for (const u of p.units) {
19
+ const uAddr = [u.street, u.house_number].filter(Boolean).join(" ");
20
+ lines.push(` • ${fmt(u.title, "Untitled")} (ID: ${u.id}) — ${fmt(u.property_status?.name)} ${uAddr ? `— ${uAddr}` : ""}`);
21
+ }
22
+ }
23
+ // Media counts
24
+ if (p.images?.length)
25
+ lines.push(`Images: ${p.images.length}`);
26
+ if (p.floorplans?.length)
27
+ lines.push(`Floorplans: ${p.floorplans.length}`);
28
+ if (p.documents?.length)
29
+ lines.push(`Documents: ${p.documents.length}`);
30
+ if (p.links?.length)
31
+ lines.push(`Links: ${p.links.length}`);
32
+ lines.push(`Created: ${fmt(p.created_at)}`);
33
+ return lines.filter(Boolean).join("\n");
34
+ }
35
+ function formatProjectRow(p) {
36
+ const title = fmt(p.title ?? p.name, "Untitled");
37
+ const cityLine = [p.zip_code, p.city].filter(Boolean).join(" ");
38
+ const unitCount = p.units?.length ?? "—";
39
+ return `| ${p.id} | ${title} | ${fmt(p.status)} | ${cityLine || "—"} | ${unitCount} |`;
40
+ }
41
+ // ── Tool registration ────────────────────────────────────────────────
42
+ export function registerProjectTools(server, client) {
43
+ // ── list_projects ───────────────────────────────────────────────
44
+ server.tool("list_projects", `List development projects in Propstack.
45
+
46
+ A project is a "super-object" that groups multiple property units
47
+ (e.g. a new-build apartment complex with 20 units).
48
+
49
+ Use this tool to:
50
+ - See all active development projects
51
+ - Get an overview of unit counts and statuses
52
+ - Find a project by name before drilling into its units
53
+
54
+ Use expand=true to include custom fields in the response.`, {
55
+ expand: z.boolean().optional()
56
+ .describe("Include custom fields in response"),
57
+ page: z.number().optional()
58
+ .describe("Page number (default: 1)"),
59
+ per_page: z.number().optional()
60
+ .describe("Results per page (default: 25)"),
61
+ }, async (args) => {
62
+ try {
63
+ const res = await client.get("/projects", { params: args });
64
+ if (!res.data || res.data.length === 0) {
65
+ return textResult("No projects found.");
66
+ }
67
+ const header = res.meta?.total_count !== undefined
68
+ ? `Found ${res.meta.total_count} projects (showing ${res.data.length}):\n\n`
69
+ : `Found ${res.data.length} projects:\n\n`;
70
+ const table = [
71
+ "| ID | Title | Status | City | Units |",
72
+ "|---|---|---|---|---|",
73
+ ...res.data.map(formatProjectRow),
74
+ ].join("\n");
75
+ return textResult(header + table);
76
+ }
77
+ catch (err) {
78
+ return errorResult("Project", err);
79
+ }
80
+ });
81
+ // ── get_project ─────────────────────────────────────────────────
82
+ server.tool("get_project", `Get full details of a single project by ID.
83
+
84
+ Returns the complete project with all units, images, floorplans,
85
+ documents, and links.
86
+
87
+ Use this tool to:
88
+ - See how a project is performing (unit statuses)
89
+ - Check how many units are still available vs. sold/reserved
90
+ - View project media and documents
91
+ - Get unit-level details (prices, sizes, statuses)`, {
92
+ id: z.number()
93
+ .describe("Project ID"),
94
+ }, async (args) => {
95
+ try {
96
+ const project = await client.get(`/projects/${args.id}`);
97
+ return textResult(formatProject(project));
98
+ }
99
+ catch (err) {
100
+ return errorResult("Project", err);
101
+ }
102
+ });
103
+ }
104
+ //# sourceMappingURL=projects.js.map