@bridgenet-tech/spear-data 1.5.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.
@@ -0,0 +1,138 @@
1
+ import { z } from "zod";
2
+ import { getPostgres } from "../db.js";
3
+ const InputSchema = z.object({
4
+ sql: z.string()
5
+ .min(1, "SQL query is required")
6
+ .describe("A read-only SQL SELECT query to run against the Spear database"),
7
+ target: z.enum(["local", "cloud"])
8
+ .default("cloud")
9
+ .describe("Which database to query: 'local' (Docker) or 'cloud' (Neon)"),
10
+ limit: z.number()
11
+ .int()
12
+ .min(1)
13
+ .max(1000)
14
+ .default(100)
15
+ .describe("Maximum rows to return (default 100, max 1000)")
16
+ }).strict();
17
+ // Extract table names from FROM/JOIN clauses
18
+ function extractTables(sql) {
19
+ const pattern = /\b(?:FROM|JOIN)\s+((?:"?\w+"?)(?:\."?\w+"?)?)/gi;
20
+ const tables = new Set();
21
+ let match;
22
+ while ((match = pattern.exec(sql)) !== null) {
23
+ const full = match[1].replace(/"/g, '').toLowerCase();
24
+ const table = full.includes('.') ? full.split('.').pop() : full;
25
+ tables.add(table);
26
+ }
27
+ return [...tables].sort();
28
+ }
29
+ // Validate that SQL is read-only
30
+ function validateReadOnly(sql) {
31
+ const normalized = sql.trim().replace(/--.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").trim();
32
+ const firstWord = normalized.split(/\s+/)[0]?.toUpperCase();
33
+ const allowed = ["SELECT", "WITH", "EXPLAIN"];
34
+ if (!firstWord || !allowed.includes(firstWord)) {
35
+ return `Only SELECT, WITH, and EXPLAIN statements are allowed. Got: ${firstWord ?? "(empty)"}`;
36
+ }
37
+ // Block dangerous patterns even in subqueries
38
+ const dangerous = /\b(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|GRANT|REVOKE|COPY|EXECUTE)\b/i;
39
+ if (dangerous.test(normalized)) {
40
+ return "Write operations are not allowed. This server is read-only.";
41
+ }
42
+ return null;
43
+ }
44
+ export function registerSqlQuery(server) {
45
+ server.registerTool("spear_sql_query", {
46
+ title: "SQL Query",
47
+ description: `Run a read-only SQL SELECT query against the Spear PSA database (Postgres).
48
+
49
+ Returns query results with full transparency metadata: target database, execution time, row count, tables referenced, and the SQL that ran.
50
+
51
+ Core tables:
52
+ - tickets: 150K+ service tickets with status, priority, SLA fields, AI fields, embeddings
53
+ - ticket_notes: 2.4M+ technician notes, activity records, and automated entries per ticket
54
+ - ticket_note_chunks: ~300K+ chunked note groups per ticket for semantic search
55
+ - teams_message_chunks: Microsoft Teams TechTeam channel messages chunked for semantic search, with channel names, authors, timestamps
56
+ - organizations: ~700 client companies with tier, location, active status
57
+ - contacts: ~6K client contacts linked to organizations
58
+ - agents: ~77 technicians/resources with roles and active status
59
+ - assets: ~7.5K managed devices with serial numbers, types, organizations
60
+ - contracts: ~1.5K service contracts with types, status, organizations
61
+ - projects: ~223 projects linked to organizations
62
+ - tasks: ~3.3K project tasks
63
+ - time_entries: ~3.3K time records with hours, dates, agents, tickets
64
+
65
+ Audit and lookup tables:
66
+ - ticket_audit_log: tracks every status, priority, agent, and queue change on tickets (who changed what, when, old/new values — essential for workflow analysis)
67
+ - ticket_statuses, ticket_priorities, ticket_types, ticket_sources, ticket_queues: lookup/reference tables
68
+ - sync_state: pipeline sync tracking
69
+ - sla_policies: SLA policy definitions
70
+
71
+ Key columns for business intelligence:
72
+ - tickets: status, priority, ticket_type, source, sla_response_due_at, sla_resolution_due_at, sla_response_breached, sla_resolution_breached, ai_category, ai_summary, created_at, completed_date
73
+ - tickets: organization_id → organizations, assigned_agent_id → agents, contact_id → contacts
74
+ - ticket_audit_log: ticket_id, field_name, old_value, new_value, changed_at, changed_by (reveals automated workflows and manual interventions)
75
+
76
+ Common patterns:
77
+ - Ticket volume by org: SELECT o.name, COUNT(*) FROM tickets t JOIN organizations o ON o.id = t.organization_id GROUP BY o.name ORDER BY count DESC
78
+ - Agent workload: SELECT agent_name, COUNT(*) FROM tickets WHERE status NOT IN ('Complete','Closed') GROUP BY assigned_agent_id
79
+ - SLA breaches: SELECT * FROM tickets WHERE sla_resolution_breached = true ORDER BY created_at DESC
80
+
81
+ Use spear_schema to see full table structures. Only SELECT, WITH (CTE), and EXPLAIN are allowed.`,
82
+ inputSchema: InputSchema,
83
+ annotations: {
84
+ readOnlyHint: true,
85
+ destructiveHint: false,
86
+ idempotentHint: true,
87
+ openWorldHint: false
88
+ }
89
+ }, async (params) => {
90
+ // Validate read-only
91
+ const error = validateReadOnly(params.sql);
92
+ if (error) {
93
+ return {
94
+ isError: true,
95
+ content: [{ type: "text", text: `Error: ${error}` }]
96
+ };
97
+ }
98
+ // Add LIMIT if not present
99
+ let sql = params.sql.trim();
100
+ if (!/\bLIMIT\b/i.test(sql)) {
101
+ sql = sql.replace(/;?\s*$/, ` LIMIT ${params.limit}`);
102
+ }
103
+ const start = performance.now();
104
+ try {
105
+ const db = getPostgres(params.target);
106
+ const rows = await db.unsafe(sql);
107
+ const ms = Math.round(performance.now() - start);
108
+ const result = {
109
+ _meta: {
110
+ source: "postgres",
111
+ target: params.target,
112
+ execution_ms: ms,
113
+ row_count: rows.length,
114
+ tables_referenced: extractTables(sql),
115
+ sql
116
+ },
117
+ rows: rows
118
+ };
119
+ return {
120
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
121
+ };
122
+ }
123
+ catch (e) {
124
+ const ms = Math.round(performance.now() - start);
125
+ return {
126
+ isError: true,
127
+ content: [{
128
+ type: "text",
129
+ text: JSON.stringify({
130
+ _meta: { source: "postgres", target: params.target, execution_ms: ms, sql },
131
+ error: e.message
132
+ }, null, 2)
133
+ }]
134
+ };
135
+ }
136
+ });
137
+ }
138
+ //# sourceMappingURL=sql-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-query.js","sourceRoot":"","sources":["../../src/tools/sql-query.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@bridgenet-tech/spear-data",
3
+ "version": "1.5.0",
4
+ "description": "Read-only MCP server for Spear PSA data exploration — Postgres + pgvector",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "spear-data": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "start": "bun run src/index.ts",
15
+ "build": "tsc",
16
+ "prepublishOnly": "npm run build",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "model-context-protocol",
22
+ "psa",
23
+ "autotask",
24
+ "postgres",
25
+ "pgvector"
26
+ ],
27
+ "license": "UNLICENSED",
28
+ "private": false,
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/bridgenet-tech/spear.git",
32
+ "directory": "mcp/spear-data"
33
+ },
34
+ "publishConfig": {
35
+ "registry": "https://registry.npmjs.org"
36
+ },
37
+ "engines": {
38
+ "node": ">=20.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.6.1",
42
+ "postgres": "^3.4.5",
43
+ "zod": "^3.23.8"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^22.0.0",
47
+ "typescript": "^5.7.2"
48
+ }
49
+ }