@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.
- package/README.md +112 -0
- package/dist/db.d.ts +13 -0
- package/dist/db.js +107 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/infra-status.d.ts +2 -0
- package/dist/tools/infra-status.js +103 -0
- package/dist/tools/infra-status.js.map +1 -0
- package/dist/tools/ingest-status.d.ts +2 -0
- package/dist/tools/ingest-status.js +105 -0
- package/dist/tools/ingest-status.js.map +1 -0
- package/dist/tools/schema.d.ts +2 -0
- package/dist/tools/schema.js +147 -0
- package/dist/tools/schema.js.map +1 -0
- package/dist/tools/semantic-search.d.ts +2 -0
- package/dist/tools/semantic-search.js +432 -0
- package/dist/tools/semantic-search.js.map +1 -0
- package/dist/tools/sql-query.d.ts +2 -0
- package/dist/tools/sql-query.js +138 -0
- package/dist/tools/sql-query.js.map +1 -0
- package/package.json +49 -0
|
@@ -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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAe,MAAM,UAAU,CAAC;AAEpD,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;SACZ,GAAG,CAAC,CAAC,EAAE,uBAAuB,CAAC;SAC/B,QAAQ,CAAC,gEAAgE,CAAC;IAC7E,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;SAC/B,OAAO,CAAC,OAAO,CAAC;SAChB,QAAQ,CAAC,6DAA6D,CAAC;IAC1E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;SACd,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,IAAI,CAAC;SACT,OAAO,CAAC,GAAG,CAAC;SACZ,QAAQ,CAAC,gDAAgD,CAAC;CAC9D,CAAC,CAAC,MAAM,EAAE,CAAC;AAIZ,6CAA6C;AAC7C,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,OAAO,GAAG,iDAAiD,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,IAAI,KAAK,CAAC;IACV,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QACjE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,iCAAiC;AACjC,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7F,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;IAE5D,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IAC9C,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC/C,OAAO,+DAA+D,SAAS,IAAI,SAAS,EAAE,CAAC;IACjG,CAAC;IAED,8CAA8C;IAC9C,MAAM,SAAS,GAAG,kFAAkF,CAAC;IACrG,IAAI,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,6DAA6D,CAAC;IACvE,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iGAkC8E;QAC3F,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,EAAE,MAAa,EAAE,EAAE;QACtB,qBAAqB;QACrB,MAAM,KAAK,GAAG,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,KAAK,EAAE,CAAC;YACV,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,KAAK,EAAE,EAAE,CAAC;aAC9D,CAAC;QACJ,CAAC;QAED,2BAA2B;QAC3B,IAAI,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;YAEjD,MAAM,MAAM,GAAG;gBACb,KAAK,EAAE;oBACL,MAAM,EAAE,UAAU;oBAClB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,YAAY,EAAE,EAAE;oBAChB,SAAS,EAAE,IAAI,CAAC,MAAM;oBACtB,iBAAiB,EAAE,aAAa,CAAC,GAAG,CAAC;oBACrC,GAAG;iBACJ;gBACD,IAAI,EAAE,IAAiC;aACxC,CAAC;YAEF,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aAC5E,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC;YACjD,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,KAAK,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,EAAE;4BAC3E,KAAK,EAAG,CAAW,CAAC,OAAO;yBAC5B,EAAE,IAAI,EAAE,CAAC,CAAC;qBACZ,CAAC;aACH,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
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
|
+
}
|