harper-knowledge 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/LICENSE +21 -0
- package/README.md +276 -0
- package/config.yaml +17 -0
- package/dist/core/embeddings.d.ts +29 -0
- package/dist/core/embeddings.js +199 -0
- package/dist/core/entries.d.ts +85 -0
- package/dist/core/entries.js +235 -0
- package/dist/core/history.d.ts +30 -0
- package/dist/core/history.js +119 -0
- package/dist/core/search.d.ts +23 -0
- package/dist/core/search.js +306 -0
- package/dist/core/tags.d.ts +32 -0
- package/dist/core/tags.js +76 -0
- package/dist/core/triage.d.ts +55 -0
- package/dist/core/triage.js +126 -0
- package/dist/http-utils.d.ts +37 -0
- package/dist/http-utils.js +132 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +76 -0
- package/dist/mcp/server.d.ts +24 -0
- package/dist/mcp/server.js +124 -0
- package/dist/mcp/tools.d.ts +13 -0
- package/dist/mcp/tools.js +497 -0
- package/dist/oauth/authorize.d.ts +27 -0
- package/dist/oauth/authorize.js +438 -0
- package/dist/oauth/github.d.ts +28 -0
- package/dist/oauth/github.js +62 -0
- package/dist/oauth/keys.d.ts +33 -0
- package/dist/oauth/keys.js +100 -0
- package/dist/oauth/metadata.d.ts +21 -0
- package/dist/oauth/metadata.js +55 -0
- package/dist/oauth/middleware.d.ts +22 -0
- package/dist/oauth/middleware.js +64 -0
- package/dist/oauth/register.d.ts +14 -0
- package/dist/oauth/register.js +83 -0
- package/dist/oauth/token.d.ts +15 -0
- package/dist/oauth/token.js +178 -0
- package/dist/oauth/validate.d.ts +30 -0
- package/dist/oauth/validate.js +52 -0
- package/dist/resources/HistoryResource.d.ts +38 -0
- package/dist/resources/HistoryResource.js +38 -0
- package/dist/resources/KnowledgeEntryResource.d.ts +64 -0
- package/dist/resources/KnowledgeEntryResource.js +157 -0
- package/dist/resources/QueryLogResource.d.ts +20 -0
- package/dist/resources/QueryLogResource.js +57 -0
- package/dist/resources/ServiceKeyResource.d.ts +51 -0
- package/dist/resources/ServiceKeyResource.js +132 -0
- package/dist/resources/TagResource.d.ts +25 -0
- package/dist/resources/TagResource.js +32 -0
- package/dist/resources/TriageResource.d.ts +51 -0
- package/dist/resources/TriageResource.js +107 -0
- package/dist/types.d.ts +317 -0
- package/dist/types.js +7 -0
- package/dist/webhooks/datadog.d.ts +26 -0
- package/dist/webhooks/datadog.js +120 -0
- package/dist/webhooks/github.d.ts +24 -0
- package/dist/webhooks/github.js +167 -0
- package/dist/webhooks/middleware.d.ts +14 -0
- package/dist/webhooks/middleware.js +161 -0
- package/dist/webhooks/types.d.ts +17 -0
- package/dist/webhooks/types.js +4 -0
- package/package.json +72 -0
- package/schema/knowledge.graphql +134 -0
- package/web/index.html +735 -0
- package/web/js/app.js +461 -0
- package/web/js/detail.js +223 -0
- package/web/js/editor.js +303 -0
- package/web/js/search.js +238 -0
- package/web/js/triage.js +305 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Utilities for Harper's stream-based request/response handling.
|
|
3
|
+
*
|
|
4
|
+
* Shared by MCP middleware, OAuth middleware, and webhook middleware.
|
|
5
|
+
*/
|
|
6
|
+
/** Maximum request body size: 1 MB */
|
|
7
|
+
const MAX_BODY_SIZE = 1_048_576;
|
|
8
|
+
/**
|
|
9
|
+
* Read the request body as a string from Harper's stream-based body.
|
|
10
|
+
*
|
|
11
|
+
* Harper's request.body is a RequestBody wrapper with .on()/.pipe() methods,
|
|
12
|
+
* not a parsed object. We need to consume the stream to get the raw text.
|
|
13
|
+
*
|
|
14
|
+
* Enforces a maximum body size to prevent memory exhaustion from oversized requests.
|
|
15
|
+
*/
|
|
16
|
+
export function readBody(request) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const body = request.body;
|
|
19
|
+
if (!body) {
|
|
20
|
+
resolve("");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// If body is already a string (unlikely but handle it)
|
|
24
|
+
if (typeof body === "string") {
|
|
25
|
+
resolve(body);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// If body is a stream with .on(), read it
|
|
29
|
+
if (typeof body.on === "function") {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
let totalSize = 0;
|
|
32
|
+
body.on("data", (chunk) => {
|
|
33
|
+
totalSize += chunk.length;
|
|
34
|
+
if (totalSize > MAX_BODY_SIZE) {
|
|
35
|
+
body.destroy?.();
|
|
36
|
+
reject(new Error("Request body too large"));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
chunks.push(Buffer.from(chunk));
|
|
40
|
+
});
|
|
41
|
+
body.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
42
|
+
body.on("error", reject);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// If body is already an object (parsed), stringify it
|
|
46
|
+
if (typeof body === "object") {
|
|
47
|
+
resolve(JSON.stringify(body));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
resolve(String(body));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build Web Standard Headers from Harper's Headers object.
|
|
55
|
+
*
|
|
56
|
+
* Harper's request.headers is a custom Headers class (iterable, with .get()),
|
|
57
|
+
* not a plain Record<string, string>.
|
|
58
|
+
*/
|
|
59
|
+
export function buildHeaders(request) {
|
|
60
|
+
const headers = new Headers();
|
|
61
|
+
const src = request.headers;
|
|
62
|
+
if (!src)
|
|
63
|
+
return headers;
|
|
64
|
+
// Harper's Headers class is iterable with [key, value] pairs
|
|
65
|
+
if (typeof src[Symbol.iterator] === "function") {
|
|
66
|
+
for (const [key, value] of src) {
|
|
67
|
+
if (value !== undefined) {
|
|
68
|
+
headers.set(key, Array.isArray(value) ? value.join(", ") : String(value));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (typeof src === "object") {
|
|
73
|
+
// Fallback: plain object
|
|
74
|
+
for (const [key, value] of Object.entries(src)) {
|
|
75
|
+
if (value !== undefined) {
|
|
76
|
+
headers.set(key, Array.isArray(value) ? value.join(", ") : String(value));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return headers;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the base URL (origin) from a Harper request.
|
|
84
|
+
*
|
|
85
|
+
* Uses the request's protocol and host properties. Defaults to
|
|
86
|
+
* http://localhost:9926 if not available.
|
|
87
|
+
*/
|
|
88
|
+
export function getBaseUrl(request) {
|
|
89
|
+
const protocol = request.protocol || "http";
|
|
90
|
+
const host = request.host || "localhost:9926";
|
|
91
|
+
return `${protocol}://${host}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse an application/x-www-form-urlencoded body into a key-value map.
|
|
95
|
+
*/
|
|
96
|
+
export function parseFormBody(body) {
|
|
97
|
+
const params = new URLSearchParams(body);
|
|
98
|
+
const result = {};
|
|
99
|
+
for (const [key, value] of params) {
|
|
100
|
+
result[key] = value;
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get a header value from Harper's request, case-insensitive.
|
|
106
|
+
*/
|
|
107
|
+
export function getHeader(request, name) {
|
|
108
|
+
const headers = request.headers;
|
|
109
|
+
if (!headers)
|
|
110
|
+
return "";
|
|
111
|
+
// Try .get() method (Harper's Headers class)
|
|
112
|
+
if (typeof headers.get === "function") {
|
|
113
|
+
const val = headers.get(name);
|
|
114
|
+
if (val !== undefined && val !== null)
|
|
115
|
+
return String(val);
|
|
116
|
+
}
|
|
117
|
+
// Try direct access (case-sensitive)
|
|
118
|
+
const direct = headers[name] ?? headers[name.toLowerCase()];
|
|
119
|
+
if (direct !== undefined) {
|
|
120
|
+
return Array.isArray(direct) ? direct[0] : String(direct);
|
|
121
|
+
}
|
|
122
|
+
// Fallback: iterate to find case-insensitive match
|
|
123
|
+
const lowerName = name.toLowerCase();
|
|
124
|
+
if (typeof headers[Symbol.iterator] === "function") {
|
|
125
|
+
for (const [key, value] of headers) {
|
|
126
|
+
if (key.toLowerCase() === lowerName && value !== undefined) {
|
|
127
|
+
return Array.isArray(value) ? value[0] : String(value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return "";
|
|
132
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* harper-knowledge — Harper Knowledge Base Plugin
|
|
3
|
+
*
|
|
4
|
+
* Sub-component plugin that provides a knowledge base with vector search,
|
|
5
|
+
* triage queue, and MCP server integration. Loaded by a parent application
|
|
6
|
+
* via `package:` in the parent's config.yaml.
|
|
7
|
+
*
|
|
8
|
+
* Harper calls handleApplication(scope) on each worker thread.
|
|
9
|
+
*/
|
|
10
|
+
import type { Scope } from "./types.ts";
|
|
11
|
+
export { createEntry, getEntry, updateEntry, deprecateEntry, linkSupersedes, linkSiblings, linkRelated, } from "./core/entries.ts";
|
|
12
|
+
export { search, filterByApplicability } from "./core/search.ts";
|
|
13
|
+
export { logEdit, getHistory } from "./core/history.ts";
|
|
14
|
+
export { listTags, syncTags } from "./core/tags.ts";
|
|
15
|
+
export { submitTriage, processTriage, listPending, dismissTriage, findBySourceId, } from "./core/triage.ts";
|
|
16
|
+
export { generateEmbedding, initEmbeddingModel, dispose as disposeEmbeddings, } from "./core/embeddings.ts";
|
|
17
|
+
export type { KnowledgeEntry, KnowledgeEntryInput, KnowledgeEntryUpdate, KnowledgeEntryEdit, TriageItem, KnowledgeTag, QueryLog, ServiceKey, SearchParams, SearchResult, ApplicabilityScope, ApplicabilityContext, TriageAction, TriageProcessOptions, KnowledgePluginConfig, } from "./types.ts";
|
|
18
|
+
/**
|
|
19
|
+
* Plugin entry point — called by Harper on each worker thread.
|
|
20
|
+
*/
|
|
21
|
+
export declare function handleApplication(scope: Scope): Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* harper-knowledge — Harper Knowledge Base Plugin
|
|
3
|
+
*
|
|
4
|
+
* Sub-component plugin that provides a knowledge base with vector search,
|
|
5
|
+
* triage queue, and MCP server integration. Loaded by a parent application
|
|
6
|
+
* via `package:` in the parent's config.yaml.
|
|
7
|
+
*
|
|
8
|
+
* Harper calls handleApplication(scope) on each worker thread.
|
|
9
|
+
*/
|
|
10
|
+
import { initEmbeddingModel, dispose as disposeEmbeddings, } from "./core/embeddings.js";
|
|
11
|
+
import { KnowledgeEntryResource } from "./resources/KnowledgeEntryResource.js";
|
|
12
|
+
import { TriageResource } from "./resources/TriageResource.js";
|
|
13
|
+
import { TagResource } from "./resources/TagResource.js";
|
|
14
|
+
import { QueryLogResource } from "./resources/QueryLogResource.js";
|
|
15
|
+
import { ServiceKeyResource } from "./resources/ServiceKeyResource.js";
|
|
16
|
+
import { HistoryResource } from "./resources/HistoryResource.js";
|
|
17
|
+
import { createMcpMiddleware } from "./mcp/server.js";
|
|
18
|
+
import { createWebhookMiddleware } from "./webhooks/middleware.js";
|
|
19
|
+
import { createOAuthMiddleware } from "./oauth/middleware.js";
|
|
20
|
+
import { ensureSigningKey } from "./oauth/keys.js";
|
|
21
|
+
// Re-export core modules for external use
|
|
22
|
+
export { createEntry, getEntry, updateEntry, deprecateEntry, linkSupersedes, linkSiblings, linkRelated, } from "./core/entries.js";
|
|
23
|
+
export { search, filterByApplicability } from "./core/search.js";
|
|
24
|
+
export { logEdit, getHistory } from "./core/history.js";
|
|
25
|
+
export { listTags, syncTags } from "./core/tags.js";
|
|
26
|
+
export { submitTriage, processTriage, listPending, dismissTriage, findBySourceId, } from "./core/triage.js";
|
|
27
|
+
export { generateEmbedding, initEmbeddingModel, dispose as disposeEmbeddings, } from "./core/embeddings.js";
|
|
28
|
+
/**
|
|
29
|
+
* Plugin entry point — called by Harper on each worker thread.
|
|
30
|
+
*/
|
|
31
|
+
export async function handleApplication(scope) {
|
|
32
|
+
const scopeLogger = scope.logger;
|
|
33
|
+
scopeLogger?.info?.("Knowledge base plugin initializing...");
|
|
34
|
+
// Read plugin configuration
|
|
35
|
+
const rawOptions = (scope.options.getAll() || {});
|
|
36
|
+
const embeddingModel = rawOptions.embeddingModel || "nomic-embed-text";
|
|
37
|
+
// Initialize the embedding model in the background (downloads on first run).
|
|
38
|
+
// Don't await — the download can take minutes and would exceed Harper's
|
|
39
|
+
// 30-second handleApplication timeout. Semantic search degrades gracefully
|
|
40
|
+
// to keyword-only mode until the model is ready.
|
|
41
|
+
initEmbeddingModel({ embeddingModel }).then(() => scopeLogger?.info?.(`Embedding model "${embeddingModel}" loaded`), (error) => scopeLogger?.error?.("Failed to initialize embedding model — semantic search will be unavailable:", error.message));
|
|
42
|
+
// Initialize OAuth signing keys (generates RSA key pair on first run)
|
|
43
|
+
try {
|
|
44
|
+
await ensureSigningKey();
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
scopeLogger?.error?.("Failed to initialize OAuth signing key:", error.message);
|
|
48
|
+
}
|
|
49
|
+
// Register REST Resource classes
|
|
50
|
+
scope.resources.set("Knowledge", KnowledgeEntryResource);
|
|
51
|
+
scope.resources.set("Triage", TriageResource);
|
|
52
|
+
scope.resources.set("KnowledgeTag", TagResource);
|
|
53
|
+
scope.resources.set("QueryLog", QueryLogResource);
|
|
54
|
+
scope.resources.set("ServiceKey", ServiceKeyResource);
|
|
55
|
+
scope.resources.set("History", HistoryResource);
|
|
56
|
+
// Register OAuth endpoints (must be first — handles /.well-known/*, /oauth/*)
|
|
57
|
+
scope.server.http?.(createOAuthMiddleware());
|
|
58
|
+
// Register webhook intake middleware (before MCP so /webhooks/* is handled first)
|
|
59
|
+
scope.server.http?.(createWebhookMiddleware(scope));
|
|
60
|
+
// Register MCP endpoint middleware (runFirst so it executes before Harper's auth layer)
|
|
61
|
+
scope.server.http?.(createMcpMiddleware(), { runFirst: true });
|
|
62
|
+
scopeLogger?.info?.("Knowledge base resources, OAuth, and MCP endpoint registered");
|
|
63
|
+
// Watch for configuration changes
|
|
64
|
+
scope.options.on("change", (_key, _value, _config) => {
|
|
65
|
+
scopeLogger?.debug?.("Knowledge base configuration changed");
|
|
66
|
+
// Future: re-configure embedding model or other settings as needed
|
|
67
|
+
});
|
|
68
|
+
// Clean up on scope close
|
|
69
|
+
scope.on("close", () => {
|
|
70
|
+
scopeLogger?.info?.("Knowledge base plugin shutting down");
|
|
71
|
+
disposeEmbeddings().catch((error) => {
|
|
72
|
+
scopeLogger?.error?.("Error disposing embedding model:", error.message);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
scopeLogger?.info?.("Knowledge base plugin initialized");
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Middleware
|
|
3
|
+
*
|
|
4
|
+
* Creates an HTTP middleware for Harper's scope.server.http() that handles
|
|
5
|
+
* MCP (Model Context Protocol) requests using Streamable HTTP transport.
|
|
6
|
+
*
|
|
7
|
+
* The middleware intercepts requests to /mcp and dispatches them through
|
|
8
|
+
* the MCP SDK's WebStandardStreamableHTTPServerTransport. Requests to
|
|
9
|
+
* other paths are passed through to the next handler.
|
|
10
|
+
*
|
|
11
|
+
* Auth: Validates JWT Bearer tokens issued by the co-located OAuth 2.1
|
|
12
|
+
* authorization server. Unauthenticated requests get 401 with
|
|
13
|
+
* WWW-Authenticate pointing to the Protected Resource Metadata endpoint.
|
|
14
|
+
*/
|
|
15
|
+
import type { HarperRequest } from "../types.ts";
|
|
16
|
+
/**
|
|
17
|
+
* Create an MCP middleware function for Harper's scope.server.http().
|
|
18
|
+
*
|
|
19
|
+
* The middleware:
|
|
20
|
+
* 1. Checks if the request pathname is /mcp or starts with /mcp/
|
|
21
|
+
* 2. If not MCP, passes through to next()
|
|
22
|
+
* 3. If MCP, validates Bearer token, then handles the request
|
|
23
|
+
*/
|
|
24
|
+
export declare function createMcpMiddleware(): (request: HarperRequest, next: (req: HarperRequest) => Promise<unknown>) => Promise<unknown>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Middleware
|
|
3
|
+
*
|
|
4
|
+
* Creates an HTTP middleware for Harper's scope.server.http() that handles
|
|
5
|
+
* MCP (Model Context Protocol) requests using Streamable HTTP transport.
|
|
6
|
+
*
|
|
7
|
+
* The middleware intercepts requests to /mcp and dispatches them through
|
|
8
|
+
* the MCP SDK's WebStandardStreamableHTTPServerTransport. Requests to
|
|
9
|
+
* other paths are passed through to the next handler.
|
|
10
|
+
*
|
|
11
|
+
* Auth: Validates JWT Bearer tokens issued by the co-located OAuth 2.1
|
|
12
|
+
* authorization server. Unauthenticated requests get 401 with
|
|
13
|
+
* WWW-Authenticate pointing to the Protected Resource Metadata endpoint.
|
|
14
|
+
*/
|
|
15
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
17
|
+
import { registerTools } from "./tools.js";
|
|
18
|
+
import { validateMcpAuth } from "../oauth/validate.js";
|
|
19
|
+
import { readBody, buildHeaders, getBaseUrl } from "../http-utils.js";
|
|
20
|
+
/** Anonymous caller for unauthenticated requests — read-only access */
|
|
21
|
+
const ANONYMOUS_CALLER = {
|
|
22
|
+
userId: "anonymous",
|
|
23
|
+
clientId: "anonymous",
|
|
24
|
+
scopes: ["mcp:read"],
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Create a new McpServer instance with all tools registered.
|
|
28
|
+
* Called per-request in stateless mode. The caller determines scope access.
|
|
29
|
+
*/
|
|
30
|
+
function createServer(caller) {
|
|
31
|
+
const server = new McpServer({
|
|
32
|
+
name: "harper-knowledge",
|
|
33
|
+
version: "0.1.0",
|
|
34
|
+
}, {
|
|
35
|
+
capabilities: {
|
|
36
|
+
tools: {},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
registerTools(server, caller);
|
|
40
|
+
return server;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create an MCP middleware function for Harper's scope.server.http().
|
|
44
|
+
*
|
|
45
|
+
* The middleware:
|
|
46
|
+
* 1. Checks if the request pathname is /mcp or starts with /mcp/
|
|
47
|
+
* 2. If not MCP, passes through to next()
|
|
48
|
+
* 3. If MCP, validates Bearer token, then handles the request
|
|
49
|
+
*/
|
|
50
|
+
export function createMcpMiddleware() {
|
|
51
|
+
return async (request, next) => {
|
|
52
|
+
const pathname = request.pathname || "";
|
|
53
|
+
// Only handle /mcp routes
|
|
54
|
+
if (pathname !== "/mcp" && !pathname.startsWith("/mcp/")) {
|
|
55
|
+
return next(request);
|
|
56
|
+
}
|
|
57
|
+
// Validate JWT Bearer token (if present)
|
|
58
|
+
const { caller, hasToken } = await validateMcpAuth(request);
|
|
59
|
+
// Invalid token → 401 so the client re-authenticates
|
|
60
|
+
if (hasToken && !caller) {
|
|
61
|
+
const baseUrl = getBaseUrl(request);
|
|
62
|
+
return new Response(JSON.stringify({
|
|
63
|
+
jsonrpc: "2.0",
|
|
64
|
+
error: {
|
|
65
|
+
code: -32001,
|
|
66
|
+
message: "Unauthorized",
|
|
67
|
+
},
|
|
68
|
+
id: null,
|
|
69
|
+
}), {
|
|
70
|
+
status: 401,
|
|
71
|
+
headers: {
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
"WWW-Authenticate": `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource"`,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// No token → anonymous read-only; valid token → authenticated caller
|
|
78
|
+
const effectiveCaller = caller ?? ANONYMOUS_CALLER;
|
|
79
|
+
try {
|
|
80
|
+
// Read the body from Harper's stream
|
|
81
|
+
const bodyText = await readBody(request);
|
|
82
|
+
const parsedBody = bodyText ? JSON.parse(bodyText) : undefined;
|
|
83
|
+
// Build an absolute URL (Request constructor requires it)
|
|
84
|
+
const host = request.host || "localhost";
|
|
85
|
+
const protocol = request.protocol || "http";
|
|
86
|
+
const absoluteUrl = `${protocol}://${host}${request.url || pathname}`;
|
|
87
|
+
// Build Web Standard headers
|
|
88
|
+
const headers = buildHeaders(request);
|
|
89
|
+
// Create a minimal Web Standard Request — body is not needed since
|
|
90
|
+
// we pass parsedBody directly to the transport
|
|
91
|
+
const webRequest = new Request(absoluteUrl, {
|
|
92
|
+
method: request.method || "POST",
|
|
93
|
+
headers,
|
|
94
|
+
body: bodyText || undefined,
|
|
95
|
+
});
|
|
96
|
+
// Stateless mode: create a fresh transport and server per request
|
|
97
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
98
|
+
enableJsonResponse: true,
|
|
99
|
+
});
|
|
100
|
+
const server = createServer(effectiveCaller);
|
|
101
|
+
await server.connect(transport);
|
|
102
|
+
// Let the MCP transport handle the request
|
|
103
|
+
const webResponse = await transport.handleRequest(webRequest, {
|
|
104
|
+
parsedBody,
|
|
105
|
+
});
|
|
106
|
+
return webResponse;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger?.error?.("MCP request handling failed:", error.message, error.stack);
|
|
110
|
+
// Return a JSON-RPC error response
|
|
111
|
+
return new Response(JSON.stringify({
|
|
112
|
+
jsonrpc: "2.0",
|
|
113
|
+
error: {
|
|
114
|
+
code: -32603,
|
|
115
|
+
message: "Internal server error",
|
|
116
|
+
},
|
|
117
|
+
id: null,
|
|
118
|
+
}), {
|
|
119
|
+
status: 500,
|
|
120
|
+
headers: { "Content-Type": "application/json" },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Registration
|
|
3
|
+
*
|
|
4
|
+
* Defines and registers all 6 MCP tools with the McpServer instance.
|
|
5
|
+
* Each tool wraps a core function from src/core/ and returns JSON-formatted results.
|
|
6
|
+
*/
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import type { ValidatedCaller } from "../oauth/validate.ts";
|
|
9
|
+
/**
|
|
10
|
+
* Register all knowledge base MCP tools on the given server.
|
|
11
|
+
* The caller determines scope access — write tools require mcp:write.
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerTools(server: McpServer, caller: ValidatedCaller): void;
|