@kansei-link/mcp-server 0.1.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/.well-known/mcp/server.json +31 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/db/connection.d.ts +4 -0
- package/dist/db/connection.js +28 -0
- package/dist/db/connection.js.map +1 -0
- package/dist/db/schema.d.ts +2 -0
- package/dist/db/schema.js +82 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/seed.d.ts +2 -0
- package/dist/db/seed.js +57 -0
- package/dist/db/seed.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +23 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/get-insights.d.ts +4 -0
- package/dist/tools/get-insights.js +109 -0
- package/dist/tools/get-insights.js.map +1 -0
- package/dist/tools/get-recipe.d.ts +4 -0
- package/dist/tools/get-recipe.js +94 -0
- package/dist/tools/get-recipe.js.map +1 -0
- package/dist/tools/report-outcome.d.ts +12 -0
- package/dist/tools/report-outcome.js +87 -0
- package/dist/tools/report-outcome.js.map +1 -0
- package/dist/tools/search-services.d.ts +4 -0
- package/dist/tools/search-services.js +127 -0
- package/dist/tools/search-services.js.map +1 -0
- package/dist/utils/confidence.d.ts +1 -0
- package/dist/utils/confidence.js +22 -0
- package/dist/utils/confidence.js.map +1 -0
- package/dist/utils/pii-masker.d.ts +4 -0
- package/dist/utils/pii-masker.js +33 -0
- package/dist/utils/pii-masker.js.map +1 -0
- package/package.json +40 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kansei-link",
|
|
3
|
+
"description": "MCP intelligence layer for discovering and orchestrating Japanese SaaS MCP tools",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"namespace": "io.github.kansei-link",
|
|
6
|
+
"homepage": "https://github.com/kansei-link/mcp-server",
|
|
7
|
+
"tools": [
|
|
8
|
+
{
|
|
9
|
+
"name": "search_services",
|
|
10
|
+
"description": "Intent-based search for Japanese SaaS MCP services"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "get_recipe",
|
|
14
|
+
"description": "Get structured workflow recipes combining multiple MCP services"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "report_outcome",
|
|
18
|
+
"description": "Report agent experience using an MCP service (anonymized, PII-masked)"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "get_insights",
|
|
22
|
+
"description": "Read aggregated agent experience data for a service"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"transports": {
|
|
26
|
+
"stdio": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["@kansei-link/mcp-server"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Synapse Arrows PTE. LTD.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# KanseiLink MCP Server
|
|
2
|
+
|
|
3
|
+
> MCP intelligence layer for discovering and orchestrating Japanese SaaS MCP tools.
|
|
4
|
+
|
|
5
|
+
KanseiLink helps AI agents find, evaluate, and combine Japanese SaaS services through the Model Context Protocol. It provides search, workflow recipes, and community-driven quality insights.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @kansei-link/mcp-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or add to your MCP client config:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"kansei-link": {
|
|
19
|
+
"command": "npx",
|
|
20
|
+
"args": ["@kansei-link/mcp-server"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
| Tool | Description |
|
|
29
|
+
|------|-------------|
|
|
30
|
+
| `search_services` | Find Japanese SaaS MCPs by intent |
|
|
31
|
+
| `get_recipe` | Get workflow patterns combining multiple services |
|
|
32
|
+
| `report_outcome` | Share your experience (anonymized) |
|
|
33
|
+
| `get_insights` | Check community data before using a service |
|
|
34
|
+
|
|
35
|
+
## Categories
|
|
36
|
+
|
|
37
|
+
CRM, Project Management, Communication, Accounting, HR, E-commerce
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Agent ←→ KanseiLink MCP Server ←→ SQLite (local)
|
|
43
|
+
↓
|
|
44
|
+
search_services → FTS5 full-text search
|
|
45
|
+
get_recipe → Workflow pattern matching
|
|
46
|
+
report_outcome → PII masking → outcomes table
|
|
47
|
+
get_insights → Aggregation + confidence scoring
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Development
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install
|
|
54
|
+
npm run build
|
|
55
|
+
npm run seed # populate with Japanese SaaS MCP data
|
|
56
|
+
npm start # start stdio server
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Security
|
|
60
|
+
|
|
61
|
+
- PII auto-masking on all text inputs
|
|
62
|
+
- Agent identity anonymized
|
|
63
|
+
- See [SECURITY.md](SECURITY.md) for full policy
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT — Synapse Arrows PTE. LTD.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
let db = null;
|
|
6
|
+
export function getDb(dbPath) {
|
|
7
|
+
if (db)
|
|
8
|
+
return db;
|
|
9
|
+
const resolvedPath = dbPath ??
|
|
10
|
+
process.env.KANSEI_DB_PATH ??
|
|
11
|
+
path.join(__dirname, "..", "..", "kansei-link.db");
|
|
12
|
+
db = new Database(resolvedPath);
|
|
13
|
+
db.pragma("journal_mode = WAL");
|
|
14
|
+
db.pragma("foreign_keys = ON");
|
|
15
|
+
return db;
|
|
16
|
+
}
|
|
17
|
+
export function getMemoryDb() {
|
|
18
|
+
const memDb = new Database(":memory:");
|
|
19
|
+
memDb.pragma("foreign_keys = ON");
|
|
20
|
+
return memDb;
|
|
21
|
+
}
|
|
22
|
+
export function closeDb() {
|
|
23
|
+
if (db) {
|
|
24
|
+
db.close();
|
|
25
|
+
db = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,IAAI,EAAE,GAA6B,IAAI,CAAC;AAExC,MAAM,UAAU,KAAK,CAAC,MAAe;IACnC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAElB,MAAM,YAAY,GAChB,MAAM;QACN,OAAO,CAAC,GAAG,CAAC,cAAc;QAC1B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAErD,EAAE,GAAG,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE/B,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC;IACvC,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,IAAI,EAAE,EAAE,CAAC;QACP,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,EAAE,GAAG,IAAI,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function initializeDb(db) {
|
|
2
|
+
db.exec(`
|
|
3
|
+
CREATE TABLE IF NOT EXISTS services (
|
|
4
|
+
id TEXT PRIMARY KEY,
|
|
5
|
+
name TEXT NOT NULL,
|
|
6
|
+
namespace TEXT,
|
|
7
|
+
description TEXT,
|
|
8
|
+
category TEXT,
|
|
9
|
+
tags TEXT,
|
|
10
|
+
mcp_endpoint TEXT,
|
|
11
|
+
trust_score REAL DEFAULT 0.5,
|
|
12
|
+
usage_count INTEGER DEFAULT 0,
|
|
13
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS recipes (
|
|
17
|
+
id TEXT PRIMARY KEY,
|
|
18
|
+
goal TEXT NOT NULL,
|
|
19
|
+
description TEXT,
|
|
20
|
+
steps TEXT NOT NULL,
|
|
21
|
+
required_services TEXT,
|
|
22
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE TABLE IF NOT EXISTS outcomes (
|
|
26
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
+
service_id TEXT NOT NULL REFERENCES services(id),
|
|
28
|
+
agent_id_hash TEXT DEFAULT 'anonymous',
|
|
29
|
+
success INTEGER NOT NULL,
|
|
30
|
+
latency_ms INTEGER,
|
|
31
|
+
error_type TEXT,
|
|
32
|
+
context_masked TEXT,
|
|
33
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS service_stats (
|
|
37
|
+
service_id TEXT PRIMARY KEY REFERENCES services(id),
|
|
38
|
+
total_calls INTEGER DEFAULT 0,
|
|
39
|
+
success_rate REAL DEFAULT 0,
|
|
40
|
+
avg_latency_ms REAL DEFAULT 0,
|
|
41
|
+
unique_agents INTEGER DEFAULT 0,
|
|
42
|
+
last_updated TEXT DEFAULT (datetime('now'))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_outcomes_service ON outcomes(service_id);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_outcomes_created ON outcomes(created_at);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_services_category ON services(category);
|
|
48
|
+
`);
|
|
49
|
+
// FTS5 virtual table for full-text search on services
|
|
50
|
+
// Check if it already exists first (CREATE VIRTUAL TABLE IF NOT EXISTS not supported in all SQLite builds)
|
|
51
|
+
const ftsExists = db
|
|
52
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='services_fts'")
|
|
53
|
+
.get();
|
|
54
|
+
if (!ftsExists) {
|
|
55
|
+
db.exec(`
|
|
56
|
+
CREATE VIRTUAL TABLE services_fts USING fts5(
|
|
57
|
+
name, description, tags, category,
|
|
58
|
+
content=services, content_rowid=rowid
|
|
59
|
+
);
|
|
60
|
+
`);
|
|
61
|
+
// Triggers to keep FTS in sync
|
|
62
|
+
db.exec(`
|
|
63
|
+
CREATE TRIGGER IF NOT EXISTS services_ai AFTER INSERT ON services BEGIN
|
|
64
|
+
INSERT INTO services_fts(rowid, name, description, tags, category)
|
|
65
|
+
VALUES (new.rowid, new.name, new.description, new.tags, new.category);
|
|
66
|
+
END;
|
|
67
|
+
|
|
68
|
+
CREATE TRIGGER IF NOT EXISTS services_ad AFTER DELETE ON services BEGIN
|
|
69
|
+
INSERT INTO services_fts(services_fts, rowid, name, description, tags, category)
|
|
70
|
+
VALUES ('delete', old.rowid, old.name, old.description, old.tags, old.category);
|
|
71
|
+
END;
|
|
72
|
+
|
|
73
|
+
CREATE TRIGGER IF NOT EXISTS services_au AFTER UPDATE ON services BEGIN
|
|
74
|
+
INSERT INTO services_fts(services_fts, rowid, name, description, tags, category)
|
|
75
|
+
VALUES ('delete', old.rowid, old.name, old.description, old.tags, old.category);
|
|
76
|
+
INSERT INTO services_fts(rowid, name, description, tags, category)
|
|
77
|
+
VALUES (new.rowid, new.name, new.description, new.tags, new.category);
|
|
78
|
+
END;
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAAC,EAAqB;IAChD,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CP,CAAC,CAAC;IAEH,sDAAsD;IACtD,2GAA2G;IAC3G,MAAM,SAAS,GAAG,EAAE;SACjB,OAAO,CACN,2EAA2E,CAC5E;SACA,GAAG,EAAE,CAAC;IAET,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,EAAE,CAAC,IAAI,CAAC;;;;;KAKP,CAAC,CAAC;QAEH,+BAA+B;QAC/B,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;KAiBP,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|
package/dist/db/seed.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getDb } from "./connection.js";
|
|
2
|
+
import { initializeDb } from "./schema.js";
|
|
3
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
function loadJson(filename) {
|
|
8
|
+
// Look in src/data first (dev), then dist/data (built)
|
|
9
|
+
const srcPath = path.join(__dirname, "..", "..", "src", "data", filename);
|
|
10
|
+
const distPath = path.join(__dirname, "..", "data", filename);
|
|
11
|
+
const filePath = existsSync(srcPath) ? srcPath : distPath;
|
|
12
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
13
|
+
}
|
|
14
|
+
export function seedDatabase(db) {
|
|
15
|
+
const services = loadJson("services-seed.json");
|
|
16
|
+
const recipes = loadJson("recipes-seed.json");
|
|
17
|
+
const insertService = db.prepare(`
|
|
18
|
+
INSERT OR IGNORE INTO services (id, name, namespace, description, category, tags, mcp_endpoint, trust_score)
|
|
19
|
+
VALUES (@id, @name, @namespace, @description, @category, @tags, @mcp_endpoint, @trust_score)
|
|
20
|
+
`);
|
|
21
|
+
const insertStats = db.prepare(`
|
|
22
|
+
INSERT OR IGNORE INTO service_stats (service_id) VALUES (@service_id)
|
|
23
|
+
`);
|
|
24
|
+
const insertRecipe = db.prepare(`
|
|
25
|
+
INSERT OR IGNORE INTO recipes (id, goal, description, steps, required_services)
|
|
26
|
+
VALUES (@id, @goal, @description, @steps, @required_services)
|
|
27
|
+
`);
|
|
28
|
+
const seedAll = db.transaction(() => {
|
|
29
|
+
for (const service of services) {
|
|
30
|
+
insertService.run(service);
|
|
31
|
+
insertStats.run({ service_id: service.id });
|
|
32
|
+
}
|
|
33
|
+
for (const recipe of recipes) {
|
|
34
|
+
insertRecipe.run({
|
|
35
|
+
...recipe,
|
|
36
|
+
steps: JSON.stringify(recipe.steps),
|
|
37
|
+
required_services: JSON.stringify(recipe.required_services),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
seedAll();
|
|
42
|
+
// Rebuild FTS index
|
|
43
|
+
db.exec("INSERT INTO services_fts(services_fts) VALUES ('rebuild')");
|
|
44
|
+
const serviceCount = db
|
|
45
|
+
.prepare("SELECT count(*) as count FROM services")
|
|
46
|
+
.get();
|
|
47
|
+
const recipeCount = db
|
|
48
|
+
.prepare("SELECT count(*) as count FROM recipes")
|
|
49
|
+
.get();
|
|
50
|
+
console.log(`Seeded ${serviceCount.count} services and ${recipeCount.count} recipes.`);
|
|
51
|
+
}
|
|
52
|
+
// Run directly
|
|
53
|
+
const db = getDb();
|
|
54
|
+
initializeDb(db);
|
|
55
|
+
seedDatabase(db);
|
|
56
|
+
db.close();
|
|
57
|
+
//# sourceMappingURL=seed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../../src/db/seed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAqB/D,SAAS,QAAQ,CAAI,QAAgB;IACnC,uDAAuD;IACvD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1D,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAA4B;IACvD,MAAM,QAAQ,GAAG,QAAQ,CAAgB,oBAAoB,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,QAAQ,CAAe,mBAAmB,CAAC,CAAC;IAE5D,MAAM,aAAa,GAAG,EAAE,CAAC,OAAO,CAAC;;;GAGhC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,EAAE,CAAC,OAAO,CAAC;;GAE9B,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,EAAE,CAAC,OAAO,CAAC;;;GAG/B,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAClC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3B,WAAW,CAAC,GAAG,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9C,CAAC;QAED,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,YAAY,CAAC,GAAG,CAAC;gBACf,GAAG,MAAM;gBACT,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC;gBACnC,iBAAiB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAAC;aAC5D,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,CAAC;IAEV,oBAAoB;IACpB,EAAE,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAErE,MAAM,YAAY,GAAG,EAAE;SACpB,OAAO,CAAC,wCAAwC,CAAC;SACjD,GAAG,EAAuB,CAAC;IAC9B,MAAM,WAAW,GAAG,EAAE;SACnB,OAAO,CAAC,uCAAuC,CAAC;SAChD,GAAG,EAAuB,CAAC;IAE9B,OAAO,CAAC,GAAG,CACT,UAAU,YAAY,CAAC,KAAK,iBAAiB,WAAW,CAAC,KAAK,WAAW,CAC1E,CAAC;AACJ,CAAC;AAED,eAAe;AACf,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;AACnB,YAAY,CAAC,EAAE,CAAC,CAAC;AACjB,YAAY,CAAC,EAAE,CAAC,CAAC;AACjB,EAAE,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createServer } from "./server.js";
|
|
4
|
+
import { closeDb } from "./db/connection.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
const server = createServer();
|
|
7
|
+
const transport = new StdioServerTransport();
|
|
8
|
+
// Clean shutdown
|
|
9
|
+
process.on("SIGINT", () => {
|
|
10
|
+
closeDb();
|
|
11
|
+
process.exit(0);
|
|
12
|
+
});
|
|
13
|
+
process.on("SIGTERM", () => {
|
|
14
|
+
closeDb();
|
|
15
|
+
process.exit(0);
|
|
16
|
+
});
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
}
|
|
19
|
+
main().catch((error) => {
|
|
20
|
+
console.error("Fatal error:", error);
|
|
21
|
+
closeDb();
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAE7C,iBAAiB;IACjB,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,EAAE,CAAC;IACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { getDb } from "./db/connection.js";
|
|
3
|
+
import { initializeDb } from "./db/schema.js";
|
|
4
|
+
import { register as registerSearchServices } from "./tools/search-services.js";
|
|
5
|
+
import { register as registerGetRecipe } from "./tools/get-recipe.js";
|
|
6
|
+
import { register as registerReportOutcome } from "./tools/report-outcome.js";
|
|
7
|
+
import { register as registerGetInsights } from "./tools/get-insights.js";
|
|
8
|
+
export function createServer() {
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name: "kansei-link",
|
|
11
|
+
version: "0.1.0",
|
|
12
|
+
});
|
|
13
|
+
// Initialize database
|
|
14
|
+
const db = getDb();
|
|
15
|
+
initializeDb(db);
|
|
16
|
+
// Register all tools
|
|
17
|
+
registerSearchServices(server, db);
|
|
18
|
+
registerGetRecipe(server, db);
|
|
19
|
+
registerReportOutcome(server, db);
|
|
20
|
+
registerGetInsights(server, db);
|
|
21
|
+
return server;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,IAAI,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,QAAQ,IAAI,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EAAE,QAAQ,IAAI,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE1E,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,sBAAsB;IACtB,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,CAAC,CAAC;IAEjB,qBAAqB;IACrB,sBAAsB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACnC,iBAAiB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9B,qBAAqB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAClC,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEhC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type Database from "better-sqlite3";
|
|
3
|
+
export declare function register(server: McpServer, db: Database.Database): void;
|
|
4
|
+
export declare function getInsights(db: Database.Database, serviceId: string): object;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { calculateConfidence } from "../utils/confidence.js";
|
|
3
|
+
export function register(server, db) {
|
|
4
|
+
server.registerTool("get_insights", {
|
|
5
|
+
title: "Get Insights",
|
|
6
|
+
description: "Get aggregated agent experience data for an MCP service. Includes success rate, latency, common errors, usage trends, and confidence score.",
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
service_id: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("ID of the MCP service to get insights for"),
|
|
11
|
+
}),
|
|
12
|
+
annotations: {
|
|
13
|
+
readOnlyHint: true,
|
|
14
|
+
openWorldHint: false,
|
|
15
|
+
},
|
|
16
|
+
}, async ({ service_id }) => {
|
|
17
|
+
const result = getInsights(db, service_id);
|
|
18
|
+
return {
|
|
19
|
+
content: [
|
|
20
|
+
{
|
|
21
|
+
type: "text",
|
|
22
|
+
text: JSON.stringify(result, null, 2),
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function getInsights(db, serviceId) {
|
|
29
|
+
// Validate service exists
|
|
30
|
+
const service = db
|
|
31
|
+
.prepare("SELECT id, name, namespace, trust_score FROM services WHERE id = ?")
|
|
32
|
+
.get(serviceId);
|
|
33
|
+
if (!service) {
|
|
34
|
+
return {
|
|
35
|
+
error: `Service '${serviceId}' not found. Use search_services to find valid service IDs.`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Get stats
|
|
39
|
+
const stats = db
|
|
40
|
+
.prepare("SELECT * FROM service_stats WHERE service_id = ?")
|
|
41
|
+
.get(serviceId);
|
|
42
|
+
if (!stats || stats.total_calls === 0) {
|
|
43
|
+
return {
|
|
44
|
+
service_id: serviceId,
|
|
45
|
+
service_name: service.name,
|
|
46
|
+
namespace: service.namespace,
|
|
47
|
+
trust_score: service.trust_score,
|
|
48
|
+
total_calls: 0,
|
|
49
|
+
message: "No usage data yet. Be the first to report_outcome for this service!",
|
|
50
|
+
confidence_score: 0,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Common errors
|
|
54
|
+
const errors = db
|
|
55
|
+
.prepare(`SELECT error_type, count(*) as count
|
|
56
|
+
FROM outcomes
|
|
57
|
+
WHERE service_id = ? AND error_type IS NOT NULL
|
|
58
|
+
GROUP BY error_type
|
|
59
|
+
ORDER BY count DESC
|
|
60
|
+
LIMIT 5`)
|
|
61
|
+
.all(serviceId);
|
|
62
|
+
// Usage trend: compare last 7 days vs previous 7 days
|
|
63
|
+
const trends = db
|
|
64
|
+
.prepare(`SELECT
|
|
65
|
+
CASE
|
|
66
|
+
WHEN created_at >= datetime('now', '-7 days') THEN 'recent'
|
|
67
|
+
WHEN created_at >= datetime('now', '-14 days') THEN 'previous'
|
|
68
|
+
END as period,
|
|
69
|
+
count(*) as calls
|
|
70
|
+
FROM outcomes
|
|
71
|
+
WHERE service_id = ? AND created_at >= datetime('now', '-14 days')
|
|
72
|
+
GROUP BY period`)
|
|
73
|
+
.all(serviceId);
|
|
74
|
+
const recentCalls = trends.find((t) => t.period === "recent")?.calls ?? 0;
|
|
75
|
+
const previousCalls = trends.find((t) => t.period === "previous")?.calls ?? 0;
|
|
76
|
+
let usageTrend;
|
|
77
|
+
if (previousCalls === 0 && recentCalls > 0)
|
|
78
|
+
usageTrend = "new_activity";
|
|
79
|
+
else if (previousCalls === 0)
|
|
80
|
+
usageTrend = "no_recent_data";
|
|
81
|
+
else {
|
|
82
|
+
const ratio = recentCalls / previousCalls;
|
|
83
|
+
if (ratio > 1.2)
|
|
84
|
+
usageTrend = "increasing";
|
|
85
|
+
else if (ratio < 0.8)
|
|
86
|
+
usageTrend = "decreasing";
|
|
87
|
+
else
|
|
88
|
+
usageTrend = "stable";
|
|
89
|
+
}
|
|
90
|
+
// Confidence score
|
|
91
|
+
const confidence = calculateConfidence(stats.unique_agents, stats.total_calls, stats.last_updated);
|
|
92
|
+
return {
|
|
93
|
+
service_id: serviceId,
|
|
94
|
+
service_name: service.name,
|
|
95
|
+
namespace: service.namespace,
|
|
96
|
+
trust_score: service.trust_score,
|
|
97
|
+
total_calls: stats.total_calls,
|
|
98
|
+
success_rate: Math.round(stats.success_rate * 100) / 100,
|
|
99
|
+
avg_latency_ms: Math.round(stats.avg_latency_ms),
|
|
100
|
+
unique_agents: stats.unique_agents,
|
|
101
|
+
common_errors: errors.length > 0
|
|
102
|
+
? errors.map((e) => ({ type: e.error_type, count: e.count }))
|
|
103
|
+
: [],
|
|
104
|
+
usage_trend: usageTrend,
|
|
105
|
+
confidence_score: Math.round(confidence * 100) / 100,
|
|
106
|
+
last_updated: stats.last_updated,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=get-insights.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-insights.js","sourceRoot":"","sources":["../../src/tools/get-insights.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type Database from "better-sqlite3";
|
|
3
|
+
export declare function register(server: McpServer, db: Database.Database): void;
|
|
4
|
+
export declare function getRecipes(db: Database.Database, goal: string, availableServices?: string[]): object[];
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, db) {
|
|
3
|
+
server.registerTool("get_recipe", {
|
|
4
|
+
title: "Get Recipe",
|
|
5
|
+
description: "Get a structured workflow recipe combining multiple MCP services. Returns step-by-step instructions with input/output mappings.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
goal: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe("What workflow you want to accomplish (e.g., 'onboard new employee', 'process invoice')"),
|
|
10
|
+
services: z
|
|
11
|
+
.array(z.string())
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Service IDs you already have access to (helps rank recipes by coverage)"),
|
|
14
|
+
}),
|
|
15
|
+
annotations: {
|
|
16
|
+
readOnlyHint: true,
|
|
17
|
+
openWorldHint: false,
|
|
18
|
+
},
|
|
19
|
+
}, async ({ goal, services }) => {
|
|
20
|
+
const results = getRecipes(db, goal, services);
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: JSON.stringify(results, null, 2),
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function getRecipes(db, goal, availableServices) {
|
|
32
|
+
// Search recipes by goal (LIKE-based for simplicity)
|
|
33
|
+
const words = goal.split(/\s+/).filter((t) => t.length > 1);
|
|
34
|
+
if (words.length === 0)
|
|
35
|
+
return [];
|
|
36
|
+
const conditions = words.map(() => `(r.goal LIKE ? OR r.description LIKE ?)`);
|
|
37
|
+
const params = [];
|
|
38
|
+
for (const word of words) {
|
|
39
|
+
const pattern = `%${word}%`;
|
|
40
|
+
params.push(pattern, pattern);
|
|
41
|
+
}
|
|
42
|
+
const query = `
|
|
43
|
+
SELECT * FROM recipes r
|
|
44
|
+
WHERE ${conditions.join(" OR ")}
|
|
45
|
+
`;
|
|
46
|
+
const recipes = db.prepare(query).all(...params);
|
|
47
|
+
// Enrich and rank results
|
|
48
|
+
const serviceCache = new Map();
|
|
49
|
+
const getService = (id) => {
|
|
50
|
+
if (serviceCache.has(id))
|
|
51
|
+
return serviceCache.get(id);
|
|
52
|
+
const svc = db
|
|
53
|
+
.prepare("SELECT id, name, namespace FROM services WHERE id = ?")
|
|
54
|
+
.get(id);
|
|
55
|
+
if (svc)
|
|
56
|
+
serviceCache.set(id, svc);
|
|
57
|
+
return svc ?? null;
|
|
58
|
+
};
|
|
59
|
+
const availableSet = new Set(availableServices ?? []);
|
|
60
|
+
return recipes
|
|
61
|
+
.map((recipe) => {
|
|
62
|
+
const steps = JSON.parse(recipe.steps);
|
|
63
|
+
const requiredServices = JSON.parse(recipe.required_services);
|
|
64
|
+
// Enrich steps with service info
|
|
65
|
+
const enrichedSteps = steps.map((step) => {
|
|
66
|
+
const svc = getService(step.service_id);
|
|
67
|
+
return {
|
|
68
|
+
...step,
|
|
69
|
+
service_name: svc?.name ?? step.service_id,
|
|
70
|
+
service_namespace: svc?.namespace ?? null,
|
|
71
|
+
available: availableSet.size > 0 ? availableSet.has(step.service_id) : null,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
// Calculate coverage
|
|
75
|
+
const coveredCount = requiredServices.filter((id) => availableSet.has(id)).length;
|
|
76
|
+
const coverage = availableSet.size > 0
|
|
77
|
+
? Math.round((coveredCount / requiredServices.length) * 100)
|
|
78
|
+
: null;
|
|
79
|
+
return {
|
|
80
|
+
recipe_id: recipe.id,
|
|
81
|
+
goal: recipe.goal,
|
|
82
|
+
description: recipe.description,
|
|
83
|
+
steps: enrichedSteps,
|
|
84
|
+
required_services: requiredServices.map((id) => ({
|
|
85
|
+
id,
|
|
86
|
+
name: getService(id)?.name ?? id,
|
|
87
|
+
available: availableSet.size > 0 ? availableSet.has(id) : null,
|
|
88
|
+
})),
|
|
89
|
+
coverage_percent: coverage,
|
|
90
|
+
};
|
|
91
|
+
})
|
|
92
|
+
.sort((a, b) => (b.coverage_percent ?? 0) - (a.coverage_percent ?? 0));
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=get-recipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-recipe.js","sourceRoot":"","sources":["../../src/tools/get-recipe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type Database from "better-sqlite3";
|
|
3
|
+
export declare function register(server: McpServer, db: Database.Database): void;
|
|
4
|
+
interface OutcomeInput {
|
|
5
|
+
service_id: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
latency_ms?: number;
|
|
8
|
+
error_type?: string;
|
|
9
|
+
context?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function reportOutcome(db: Database.Database, input: OutcomeInput): object;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { maskPii } from "../utils/pii-masker.js";
|
|
3
|
+
export function register(server, db) {
|
|
4
|
+
server.registerTool("report_outcome", {
|
|
5
|
+
title: "Report Outcome",
|
|
6
|
+
description: "Report your experience using an MCP service. Helps other agents make better decisions. All data is anonymized and PII is auto-masked.",
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
service_id: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("ID of the MCP service you used"),
|
|
11
|
+
success: z
|
|
12
|
+
.boolean()
|
|
13
|
+
.describe("Whether the operation succeeded"),
|
|
14
|
+
latency_ms: z
|
|
15
|
+
.number()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Response time in milliseconds"),
|
|
18
|
+
error_type: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Error category if failed (e.g., 'auth_error', 'timeout', 'rate_limit', 'invalid_input')"),
|
|
22
|
+
context: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Additional context about the usage (PII will be auto-masked)"),
|
|
26
|
+
}),
|
|
27
|
+
annotations: {
|
|
28
|
+
readOnlyHint: false,
|
|
29
|
+
idempotentHint: false,
|
|
30
|
+
},
|
|
31
|
+
}, async ({ service_id, success, latency_ms, error_type, context }) => {
|
|
32
|
+
const result = reportOutcome(db, {
|
|
33
|
+
service_id,
|
|
34
|
+
success,
|
|
35
|
+
latency_ms,
|
|
36
|
+
error_type,
|
|
37
|
+
context,
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: JSON.stringify(result, null, 2),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function reportOutcome(db, input) {
|
|
50
|
+
// Validate service exists
|
|
51
|
+
const service = db
|
|
52
|
+
.prepare("SELECT id, name FROM services WHERE id = ?")
|
|
53
|
+
.get(input.service_id);
|
|
54
|
+
if (!service) {
|
|
55
|
+
return {
|
|
56
|
+
recorded: false,
|
|
57
|
+
error: `Service '${input.service_id}' not found. Use search_services to find valid service IDs.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Mask PII in context
|
|
61
|
+
let contextMasked = null;
|
|
62
|
+
let maskedFields = [];
|
|
63
|
+
if (input.context) {
|
|
64
|
+
const result = maskPii(input.context);
|
|
65
|
+
contextMasked = result.masked;
|
|
66
|
+
maskedFields = result.maskedFields;
|
|
67
|
+
}
|
|
68
|
+
// Insert outcome
|
|
69
|
+
db.prepare(`INSERT INTO outcomes (service_id, agent_id_hash, success, latency_ms, error_type, context_masked)
|
|
70
|
+
VALUES (?, 'anonymous', ?, ?, ?, ?)`).run(input.service_id, input.success ? 1 : 0, input.latency_ms ?? null, input.error_type ?? null, contextMasked);
|
|
71
|
+
// Update aggregated stats
|
|
72
|
+
db.prepare(`INSERT INTO service_stats (service_id, total_calls, success_rate, avg_latency_ms, unique_agents, last_updated)
|
|
73
|
+
VALUES (?, 1, ?, ?, 1, datetime('now'))
|
|
74
|
+
ON CONFLICT(service_id) DO UPDATE SET
|
|
75
|
+
total_calls = (SELECT count(*) FROM outcomes WHERE service_id = ?),
|
|
76
|
+
success_rate = (SELECT avg(success) FROM outcomes WHERE service_id = ?),
|
|
77
|
+
avg_latency_ms = COALESCE((SELECT avg(latency_ms) FROM outcomes WHERE service_id = ? AND latency_ms IS NOT NULL), 0),
|
|
78
|
+
unique_agents = (SELECT count(DISTINCT agent_id_hash) FROM outcomes WHERE service_id = ?),
|
|
79
|
+
last_updated = datetime('now')`).run(input.service_id, input.success ? 1.0 : 0.0, input.latency_ms ?? 0, input.service_id, input.service_id, input.service_id, input.service_id);
|
|
80
|
+
return {
|
|
81
|
+
recorded: true,
|
|
82
|
+
service_id: input.service_id,
|
|
83
|
+
service_name: service.name,
|
|
84
|
+
masked_fields: maskedFields.length > 0 ? maskedFields : undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=report-outcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-outcome.js","sourceRoot":"","sources":["../../src/tools/report-outcome.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type Database from "better-sqlite3";
|
|
3
|
+
export declare function register(server: McpServer, db: Database.Database): void;
|
|
4
|
+
export declare function searchServices(db: Database.Database, intent: string, category?: string, limit?: number): object[];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function register(server, db) {
|
|
3
|
+
server.registerTool("search_services", {
|
|
4
|
+
title: "Search Services",
|
|
5
|
+
description: "Search for Japanese SaaS MCP services by intent or category. Returns ranked results with trust scores and usage data.",
|
|
6
|
+
inputSchema: z.object({
|
|
7
|
+
intent: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe("What you want to accomplish (e.g., 'send invoice', 'manage employees', 'track attendance')"),
|
|
10
|
+
category: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Filter by category: crm, project_management, communication, accounting, hr, ecommerce"),
|
|
14
|
+
limit: z
|
|
15
|
+
.number()
|
|
16
|
+
.optional()
|
|
17
|
+
.default(5)
|
|
18
|
+
.describe("Max results to return (default: 5)"),
|
|
19
|
+
}),
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: true,
|
|
22
|
+
openWorldHint: false,
|
|
23
|
+
},
|
|
24
|
+
}, async ({ intent, category, limit }) => {
|
|
25
|
+
const results = searchServices(db, intent, category, limit ?? 5);
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: JSON.stringify(results, null, 2),
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function searchServices(db, intent, category, limit = 5) {
|
|
37
|
+
// Try FTS5 search first
|
|
38
|
+
let results = ftsSearch(db, intent, category, limit);
|
|
39
|
+
// Fallback to LIKE search if FTS returns nothing
|
|
40
|
+
if (results.length === 0) {
|
|
41
|
+
results = likeSearch(db, intent, category, limit);
|
|
42
|
+
}
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
function ftsSearch(db, intent, category, limit) {
|
|
46
|
+
// Tokenize intent for FTS query (simple word splitting)
|
|
47
|
+
const tokens = intent
|
|
48
|
+
.split(/\s+/)
|
|
49
|
+
.filter((t) => t.length > 1)
|
|
50
|
+
.map((t) => `"${t}"`)
|
|
51
|
+
.join(" OR ");
|
|
52
|
+
if (!tokens)
|
|
53
|
+
return [];
|
|
54
|
+
try {
|
|
55
|
+
// Join FTS results back to services table via rowid
|
|
56
|
+
let query = `
|
|
57
|
+
SELECT s.*, ss.total_calls, ss.success_rate, fts.rank as fts_rank
|
|
58
|
+
FROM services_fts fts
|
|
59
|
+
JOIN services s ON s.rowid = fts.rowid
|
|
60
|
+
LEFT JOIN service_stats ss ON s.id = ss.service_id
|
|
61
|
+
WHERE services_fts MATCH ?
|
|
62
|
+
`;
|
|
63
|
+
const params = [tokens];
|
|
64
|
+
if (category) {
|
|
65
|
+
query += ` AND s.category = ?`;
|
|
66
|
+
params.push(category);
|
|
67
|
+
}
|
|
68
|
+
query += ` ORDER BY fts.rank LIMIT ?`;
|
|
69
|
+
params.push(limit);
|
|
70
|
+
const services = db.prepare(query).all(...params);
|
|
71
|
+
if (services.length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
return services.map((s) => ({
|
|
74
|
+
service_id: s.id,
|
|
75
|
+
name: s.name,
|
|
76
|
+
namespace: s.namespace,
|
|
77
|
+
description: s.description,
|
|
78
|
+
category: s.category,
|
|
79
|
+
mcp_endpoint: s.mcp_endpoint,
|
|
80
|
+
trust_score: s.trust_score,
|
|
81
|
+
usage_count: s.total_calls ?? 0,
|
|
82
|
+
success_rate: s.success_rate ?? null,
|
|
83
|
+
relevance_score: Math.round((1 / (1 + Math.abs(s.fts_rank)) + s.trust_score * 0.3) * 100) / 100,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// FTS query may fail on certain inputs, fall through to LIKE
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function likeSearch(db, intent, category, limit) {
|
|
92
|
+
const words = intent.split(/\s+/).filter((t) => t.length > 1);
|
|
93
|
+
if (words.length === 0)
|
|
94
|
+
return [];
|
|
95
|
+
const conditions = words.map(() => `(s.name LIKE ? OR s.description LIKE ? OR s.tags LIKE ?)`);
|
|
96
|
+
const params = [];
|
|
97
|
+
for (const word of words) {
|
|
98
|
+
const pattern = `%${word}%`;
|
|
99
|
+
params.push(pattern, pattern, pattern);
|
|
100
|
+
}
|
|
101
|
+
let query = `
|
|
102
|
+
SELECT s.*, ss.total_calls, ss.success_rate
|
|
103
|
+
FROM services s
|
|
104
|
+
LEFT JOIN service_stats ss ON s.id = ss.service_id
|
|
105
|
+
WHERE (${conditions.join(" OR ")})
|
|
106
|
+
`;
|
|
107
|
+
if (category) {
|
|
108
|
+
query += ` AND s.category = ?`;
|
|
109
|
+
params.push(category);
|
|
110
|
+
}
|
|
111
|
+
query += ` ORDER BY s.trust_score DESC LIMIT ?`;
|
|
112
|
+
params.push(limit);
|
|
113
|
+
const services = db.prepare(query).all(...params);
|
|
114
|
+
return services.map((s) => ({
|
|
115
|
+
service_id: s.id,
|
|
116
|
+
name: s.name,
|
|
117
|
+
namespace: s.namespace,
|
|
118
|
+
description: s.description,
|
|
119
|
+
category: s.category,
|
|
120
|
+
mcp_endpoint: s.mcp_endpoint,
|
|
121
|
+
trust_score: s.trust_score,
|
|
122
|
+
usage_count: s.total_calls ?? 0,
|
|
123
|
+
success_rate: s.success_rate ?? null,
|
|
124
|
+
relevance_score: s.trust_score,
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=search-services.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-services.js","sourceRoot":"","sources":["../../src/tools/search-services.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function calculateConfidence(uniqueAgents: number, totalCalls: number, lastUpdated: string | null): number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function calculateConfidence(uniqueAgents, totalCalls, lastUpdated) {
|
|
2
|
+
if (totalCalls === 0)
|
|
3
|
+
return 0;
|
|
4
|
+
// Agent diversity factor (0-0.4): more unique agents = more trustworthy
|
|
5
|
+
const agentFactor = Math.min(uniqueAgents / 5, 1) * 0.4;
|
|
6
|
+
// Volume factor (0-0.3): more calls = more reliable stats
|
|
7
|
+
const volumeFactor = Math.min(totalCalls / 50, 1) * 0.3;
|
|
8
|
+
// Recency factor (0-0.3): fresher data = more relevant
|
|
9
|
+
let recencyFactor = 0.1; // default: very old
|
|
10
|
+
if (lastUpdated) {
|
|
11
|
+
const daysSince = (Date.now() - new Date(lastUpdated).getTime()) / (1000 * 60 * 60 * 24);
|
|
12
|
+
if (daysSince < 7)
|
|
13
|
+
recencyFactor = 1.0;
|
|
14
|
+
else if (daysSince < 30)
|
|
15
|
+
recencyFactor = 0.7;
|
|
16
|
+
else if (daysSince < 90)
|
|
17
|
+
recencyFactor = 0.4;
|
|
18
|
+
}
|
|
19
|
+
recencyFactor *= 0.3;
|
|
20
|
+
return Math.min(1.0, agentFactor + volumeFactor + recencyFactor);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=confidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confidence.js","sourceRoot":"","sources":["../../src/utils/confidence.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,mBAAmB,CACjC,YAAoB,EACpB,UAAkB,EAClB,WAA0B;IAE1B,IAAI,UAAU,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAE/B,wEAAwE;IACxE,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC;IAExD,0DAA0D;IAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC;IAExD,uDAAuD;IACvD,IAAI,aAAa,GAAG,GAAG,CAAC,CAAC,oBAAoB;IAC7C,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,SAAS,GACb,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACzE,IAAI,SAAS,GAAG,CAAC;YAAE,aAAa,GAAG,GAAG,CAAC;aAClC,IAAI,SAAS,GAAG,EAAE;YAAE,aAAa,GAAG,GAAG,CAAC;aACxC,IAAI,SAAS,GAAG,EAAE;YAAE,aAAa,GAAG,GAAG,CAAC;IAC/C,CAAC;IACD,aAAa,IAAI,GAAG,CAAC;IAErB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,YAAY,GAAG,aAAa,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const patterns = [
|
|
2
|
+
// Email addresses (must be before IP to avoid partial matches)
|
|
3
|
+
{
|
|
4
|
+
regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
5
|
+
replacement: "[EMAIL]",
|
|
6
|
+
},
|
|
7
|
+
// IP addresses (must be before phone to avoid 192.168.1.1 matching as phone)
|
|
8
|
+
{ regex: /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g, replacement: "[IP]" },
|
|
9
|
+
// Japanese phone numbers: 03-1234-5678, 090-1234-5678, 0120-123-456
|
|
10
|
+
{ regex: /0\d{1,4}-\d{1,4}-\d{3,4}/g, replacement: "[PHONE]" },
|
|
11
|
+
// International phone: +81-3-1234-5678 (require + prefix to avoid false positives)
|
|
12
|
+
{
|
|
13
|
+
regex: /\+\d{1,3}[-.\s]\d{1,4}[-.\s]\d{1,4}[-.\s]?\d{0,4}/g,
|
|
14
|
+
replacement: "[PHONE]",
|
|
15
|
+
},
|
|
16
|
+
// Katakana full names (two or more katakana words separated by space)
|
|
17
|
+
{ regex: /[\u30A0-\u30FF]{2,}[\s\u3000][\u30A0-\u30FF]{2,}/g, replacement: "[NAME]" },
|
|
18
|
+
];
|
|
19
|
+
export function maskPii(text) {
|
|
20
|
+
const maskedFields = [];
|
|
21
|
+
let result = text;
|
|
22
|
+
for (const { regex, replacement } of patterns) {
|
|
23
|
+
// Reset regex lastIndex for global patterns
|
|
24
|
+
regex.lastIndex = 0;
|
|
25
|
+
if (regex.test(result)) {
|
|
26
|
+
maskedFields.push(replacement.replace(/[[\]]/g, ""));
|
|
27
|
+
regex.lastIndex = 0;
|
|
28
|
+
result = result.replace(regex, replacement);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { masked: result, maskedFields };
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=pii-masker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pii-masker.js","sourceRoot":"","sources":["../../src/utils/pii-masker.ts"],"names":[],"mappings":"AAAA,MAAM,QAAQ,GAAkD;IAC9D,+DAA+D;IAC/D;QACE,KAAK,EAAE,iDAAiD;QACxD,WAAW,EAAE,SAAS;KACvB;IACD,6EAA6E;IAC7E,EAAE,KAAK,EAAE,qCAAqC,EAAE,WAAW,EAAE,MAAM,EAAE;IACrE,oEAAoE;IACpE,EAAE,KAAK,EAAE,2BAA2B,EAAE,WAAW,EAAE,SAAS,EAAE;IAC9D,mFAAmF;IACnF;QACE,KAAK,EAAE,oDAAoD;QAC3D,WAAW,EAAE,SAAS;KACvB;IACD,sEAAsE;IACtE,EAAE,KAAK,EAAE,mDAAmD,EAAE,WAAW,EAAE,QAAQ,EAAE;CACtF,CAAC;AAEF,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,IAAI,MAAM,GAAG,IAAI,CAAC;IAElB,KAAK,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC9C,4CAA4C;QAC5C,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;QACpB,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACvB,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;YACrD,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;YACpB,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;AAC1C,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kansei-link/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP intelligence layer for discovering and orchestrating Japanese SaaS MCP tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"kansei-link-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
".well-known"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"seed": "node dist/db/seed.js",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"japanese-saas",
|
|
24
|
+
"agent-engine-optimization",
|
|
25
|
+
"aeo",
|
|
26
|
+
"kansei-link"
|
|
27
|
+
],
|
|
28
|
+
"author": "Synapse Arrows PTE. LTD.",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
32
|
+
"better-sqlite3": "^11.7.0",
|
|
33
|
+
"zod": "^3.24.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
37
|
+
"@types/node": "^22.10.0",
|
|
38
|
+
"typescript": "^5.7.0"
|
|
39
|
+
}
|
|
40
|
+
}
|