@dain-os/mcp-server 0.11.0 → 0.13.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/dist/http-util.d.ts +24 -0
- package/dist/http-util.d.ts.map +1 -0
- package/dist/http-util.js +113 -0
- package/dist/http-util.js.map +1 -0
- package/dist/http.d.ts +5 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +92 -32
- package/dist/http.js.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/meta.d.ts +27 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +81 -0
- package/dist/meta.js.map +1 -0
- package/dist/oauth/config.d.ts +57 -0
- package/dist/oauth/config.d.ts.map +1 -0
- package/dist/oauth/config.js +74 -0
- package/dist/oauth/config.js.map +1 -0
- package/dist/oauth/consent-page.d.ts +24 -0
- package/dist/oauth/consent-page.d.ts.map +1 -0
- package/dist/oauth/consent-page.js +98 -0
- package/dist/oauth/consent-page.js.map +1 -0
- package/dist/oauth/crypto.d.ts +48 -0
- package/dist/oauth/crypto.d.ts.map +1 -0
- package/dist/oauth/crypto.js +72 -0
- package/dist/oauth/crypto.js.map +1 -0
- package/dist/oauth/handlers.d.ts +89 -0
- package/dist/oauth/handlers.d.ts.map +1 -0
- package/dist/oauth/handlers.js +371 -0
- package/dist/oauth/handlers.js.map +1 -0
- package/dist/oauth/metadata.d.ts +29 -0
- package/dist/oauth/metadata.d.ts.map +1 -0
- package/dist/oauth/metadata.js +33 -0
- package/dist/oauth/metadata.js.map +1 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +8 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/semantic/developer.d.ts.map +1 -1
- package/dist/tools/semantic/developer.js +6 -0
- package/dist/tools/semantic/developer.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level HTTP helpers shared by the MCP JSON-RPC handler and the OAuth
|
|
3
|
+
* endpoints. Centralises the CORS headers and the handful of response shapes
|
|
4
|
+
* we emit (JSON, HTML, redirect, no-content) plus tolerant body parsing.
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
7
|
+
export declare const CORS_HEADERS: Record<string, string>;
|
|
8
|
+
export declare function sendJson(res: ServerResponse, status: number, body: unknown, extraHeaders?: Record<string, string>): void;
|
|
9
|
+
export declare function sendHtml(res: ServerResponse, status: number, html: string): void;
|
|
10
|
+
export declare function sendRedirect(res: ServerResponse, location: string): void;
|
|
11
|
+
export declare function sendNoContent(res: ServerResponse, status: number): void;
|
|
12
|
+
interface WithBody {
|
|
13
|
+
body?: unknown;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse an `application/x-www-form-urlencoded` (or JSON) body into a flat
|
|
17
|
+
* string map. Tolerant of @vercel/node having already parsed `req.body` into
|
|
18
|
+
* an object, a string, or nothing.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseFormBody(req: IncomingMessage & WithBody): Promise<Record<string, string>>;
|
|
21
|
+
/** Parse a JSON body, preserving arrays/objects (used by Dynamic Client Registration). */
|
|
22
|
+
export declare function parseJsonBody(req: IncomingMessage & WithBody): Promise<unknown>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=http-util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-util.d.ts","sourceRoot":"","sources":["../src/http-util.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAI/C,CAAC;AAMF,wBAAgB,QAAQ,CACtB,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,IAAI,CAMN;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAOhF;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAKxE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAIvE;AAmBD,UAAU,QAAQ;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAsBpG;AAED,0FAA0F;AAC1F,wBAAsB,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAerF"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level HTTP helpers shared by the MCP JSON-RPC handler and the OAuth
|
|
3
|
+
* endpoints. Centralises the CORS headers and the handful of response shapes
|
|
4
|
+
* we emit (JSON, HTML, redirect, no-content) plus tolerant body parsing.
|
|
5
|
+
*/
|
|
6
|
+
export const CORS_HEADERS = {
|
|
7
|
+
'Access-Control-Allow-Origin': '*',
|
|
8
|
+
'Access-Control-Allow-Methods': 'POST, GET, DELETE, OPTIONS',
|
|
9
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, Mcp-Session-Id',
|
|
10
|
+
};
|
|
11
|
+
function applyCors(res) {
|
|
12
|
+
for (const [k, v] of Object.entries(CORS_HEADERS))
|
|
13
|
+
res.setHeader(k, v);
|
|
14
|
+
}
|
|
15
|
+
export function sendJson(res, status, body, extraHeaders) {
|
|
16
|
+
applyCors(res);
|
|
17
|
+
if (extraHeaders)
|
|
18
|
+
for (const [k, v] of Object.entries(extraHeaders))
|
|
19
|
+
res.setHeader(k, v);
|
|
20
|
+
res.setHeader('Content-Type', 'application/json');
|
|
21
|
+
res.writeHead(status);
|
|
22
|
+
res.end(JSON.stringify(body));
|
|
23
|
+
}
|
|
24
|
+
export function sendHtml(res, status, html) {
|
|
25
|
+
applyCors(res);
|
|
26
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
27
|
+
// The consent page must never be cached: it carries per-request OAuth params.
|
|
28
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
29
|
+
res.writeHead(status);
|
|
30
|
+
res.end(html);
|
|
31
|
+
}
|
|
32
|
+
export function sendRedirect(res, location) {
|
|
33
|
+
applyCors(res);
|
|
34
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
35
|
+
res.writeHead(302, { Location: location });
|
|
36
|
+
res.end();
|
|
37
|
+
}
|
|
38
|
+
export function sendNoContent(res, status) {
|
|
39
|
+
applyCors(res);
|
|
40
|
+
res.writeHead(status);
|
|
41
|
+
res.end();
|
|
42
|
+
}
|
|
43
|
+
function recordFromObject(obj) {
|
|
44
|
+
const out = {};
|
|
45
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
46
|
+
if (v === undefined || v === null)
|
|
47
|
+
continue;
|
|
48
|
+
out[k] = Array.isArray(v) ? String(v[0]) : String(v);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
async function readRawBody(req) {
|
|
53
|
+
const chunks = [];
|
|
54
|
+
for await (const chunk of req) {
|
|
55
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
56
|
+
}
|
|
57
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parse an `application/x-www-form-urlencoded` (or JSON) body into a flat
|
|
61
|
+
* string map. Tolerant of @vercel/node having already parsed `req.body` into
|
|
62
|
+
* an object, a string, or nothing.
|
|
63
|
+
*/
|
|
64
|
+
export async function parseFormBody(req) {
|
|
65
|
+
const body = req.body;
|
|
66
|
+
if (body && typeof body === 'object' && !Buffer.isBuffer(body)) {
|
|
67
|
+
return recordFromObject(body);
|
|
68
|
+
}
|
|
69
|
+
let raw;
|
|
70
|
+
if (typeof body === 'string')
|
|
71
|
+
raw = body;
|
|
72
|
+
else if (Buffer.isBuffer(body))
|
|
73
|
+
raw = body.toString('utf8');
|
|
74
|
+
else
|
|
75
|
+
raw = await readRawBody(req);
|
|
76
|
+
const trimmed = raw.trim();
|
|
77
|
+
if (trimmed.startsWith('{')) {
|
|
78
|
+
try {
|
|
79
|
+
return recordFromObject(JSON.parse(trimmed));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// fall through to urlencoded parsing
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const out = {};
|
|
86
|
+
for (const [k, v] of new URLSearchParams(raw))
|
|
87
|
+
out[k] = v;
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
/** Parse a JSON body, preserving arrays/objects (used by Dynamic Client Registration). */
|
|
91
|
+
export async function parseJsonBody(req) {
|
|
92
|
+
const body = req.body;
|
|
93
|
+
if (body && typeof body === 'object' && !Buffer.isBuffer(body))
|
|
94
|
+
return body;
|
|
95
|
+
let raw;
|
|
96
|
+
if (typeof body === 'string')
|
|
97
|
+
raw = body;
|
|
98
|
+
else if (Buffer.isBuffer(body))
|
|
99
|
+
raw = body.toString('utf8');
|
|
100
|
+
else
|
|
101
|
+
raw = await readRawBody(req);
|
|
102
|
+
if (!raw.trim())
|
|
103
|
+
return {};
|
|
104
|
+
// Never let a raw SyntaxError escape to the top-level handler (it would
|
|
105
|
+
// surface as a 500 + stack trace). Callers turn this into a structured 400.
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(raw);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new SyntaxError('Invalid JSON in request body');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=http-util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-util.js","sourceRoot":"","sources":["../src/http-util.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Implements the MCP Streamable HTTP protocol subset needed for stateless
|
|
10
10
|
* tool servers: initialize, notifications/initialized, tools/list, tools/call.
|
|
11
|
+
*
|
|
12
|
+
* Authentication accepts EITHER:
|
|
13
|
+
* - a raw `dain_pat_...` Bearer token (Claude Code / stdio clients), or
|
|
14
|
+
* - an OAuth access token minted by the embedded authorization server
|
|
15
|
+
* (Claude in the browser, which only speaks OAuth). See ./oauth/*.
|
|
11
16
|
*/
|
|
12
17
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
13
18
|
interface VercelRequest extends IncomingMessage {
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAkBjE,UAAU,aAAc,SAAQ,eAAe;IAC7C,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA2JD,wBAA8B,OAAO,CACnC,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,IAAI,CAAC,CAmEf"}
|
package/dist/http.js
CHANGED
|
@@ -8,25 +8,20 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Implements the MCP Streamable HTTP protocol subset needed for stateless
|
|
10
10
|
* tool servers: initialize, notifications/initialized, tools/list, tools/call.
|
|
11
|
+
*
|
|
12
|
+
* Authentication accepts EITHER:
|
|
13
|
+
* - a raw `dain_pat_...` Bearer token (Claude Code / stdio clients), or
|
|
14
|
+
* - an OAuth access token minted by the embedded authorization server
|
|
15
|
+
* (Claude in the browser, which only speaks OAuth). See ./oauth/*.
|
|
11
16
|
*/
|
|
12
17
|
import { DainOsClient, DainOsApiError } from './client.js';
|
|
13
18
|
import { allTools, getTool } from './tools/index.js';
|
|
14
19
|
import { toJsonSchema } from './tools/types.js';
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
import { SERVER_NAME, SERVER_VERSION, SERVER_INSTRUCTIONS } from './meta.js';
|
|
21
|
+
import { CORS_HEADERS, sendJson, sendNoContent } from './http-util.js';
|
|
22
|
+
import { isOAuthConfigured, resolveBaseUrl } from './oauth/config.js';
|
|
23
|
+
import { handleAuthorizationServerMetadata, handleAuthorize, handleProtectedResourceMetadata, handleRegister, handleToken, recoverPat, } from './oauth/handlers.js';
|
|
17
24
|
const PROTOCOL_VERSION = '2025-03-26';
|
|
18
|
-
const CORS_HEADERS = {
|
|
19
|
-
'Access-Control-Allow-Origin': '*',
|
|
20
|
-
'Access-Control-Allow-Methods': 'POST, GET, DELETE, OPTIONS',
|
|
21
|
-
'Access-Control-Allow-Headers': 'Content-Type, Authorization, Mcp-Session-Id',
|
|
22
|
-
};
|
|
23
|
-
function sendJson(res, status, body) {
|
|
24
|
-
for (const [k, v] of Object.entries(CORS_HEADERS))
|
|
25
|
-
res.setHeader(k, v);
|
|
26
|
-
res.setHeader('Content-Type', 'application/json');
|
|
27
|
-
res.writeHead(status);
|
|
28
|
-
res.end(JSON.stringify(body));
|
|
29
|
-
}
|
|
30
25
|
function jsonRpcResult(id, result) {
|
|
31
26
|
return { jsonrpc: '2.0', result, id: id ?? null };
|
|
32
27
|
}
|
|
@@ -37,7 +32,8 @@ function handleInitialize(req) {
|
|
|
37
32
|
return jsonRpcResult(req.id, {
|
|
38
33
|
protocolVersion: PROTOCOL_VERSION,
|
|
39
34
|
capabilities: { tools: { listChanged: false } },
|
|
40
|
-
serverInfo: { name:
|
|
35
|
+
serverInfo: { name: SERVER_NAME, version: SERVER_VERSION },
|
|
36
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
41
37
|
});
|
|
42
38
|
}
|
|
43
39
|
function handleToolsList(req) {
|
|
@@ -83,24 +79,23 @@ async function handleToolsCall(req, client) {
|
|
|
83
79
|
});
|
|
84
80
|
}
|
|
85
81
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
sendJson(res, 405, jsonRpcError(null, -32000, 'Method not allowed. Use POST for MCP requests.'));
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
82
|
+
/**
|
|
83
|
+
* Build the WWW-Authenticate challenge that tells an OAuth-capable client where
|
|
84
|
+
* to find this server's protected-resource metadata, kicking off the flow.
|
|
85
|
+
*/
|
|
86
|
+
function bearerChallenge(baseUrl) {
|
|
87
|
+
return (`Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource", ` +
|
|
88
|
+
'error="invalid_token", error_description="Authentication required"');
|
|
89
|
+
}
|
|
90
|
+
async function handleMcp(req, res, baseUrl) {
|
|
98
91
|
const authHeader = req.headers['authorization'];
|
|
99
|
-
const
|
|
92
|
+
const bearer = typeof authHeader === 'string' && authHeader.startsWith('Bearer ')
|
|
100
93
|
? authHeader.slice(7)
|
|
101
94
|
: null;
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
const pat = await recoverPat(bearer);
|
|
96
|
+
if (!pat) {
|
|
97
|
+
const extra = isOAuthConfigured() ? { 'WWW-Authenticate': bearerChallenge(baseUrl) } : undefined;
|
|
98
|
+
sendJson(res, 401, jsonRpcError(null, -32001, 'Missing or invalid credentials. Provide a Bearer dain_pat_... token, or connect via OAuth.'), extra);
|
|
104
99
|
return;
|
|
105
100
|
}
|
|
106
101
|
const body = req.body;
|
|
@@ -109,7 +104,7 @@ export default async function handler(req, res) {
|
|
|
109
104
|
return;
|
|
110
105
|
}
|
|
111
106
|
const apiUrl = process.env['DAINOS_API_URL'];
|
|
112
|
-
const client = new DainOsClient(apiUrl,
|
|
107
|
+
const client = new DainOsClient(apiUrl, pat);
|
|
113
108
|
// Handle batch requests (array) and single requests
|
|
114
109
|
const requests = Array.isArray(body) ? body : [body];
|
|
115
110
|
const responses = [];
|
|
@@ -136,9 +131,74 @@ export default async function handler(req, res) {
|
|
|
136
131
|
}
|
|
137
132
|
if (responses.length === 0) {
|
|
138
133
|
// All requests were notifications
|
|
139
|
-
|
|
134
|
+
sendNoContent(res, 202);
|
|
140
135
|
return;
|
|
141
136
|
}
|
|
142
137
|
sendJson(res, 200, Array.isArray(body) ? responses : responses[0]);
|
|
143
138
|
}
|
|
139
|
+
export default async function handler(req, res) {
|
|
140
|
+
if (req.method === 'OPTIONS') {
|
|
141
|
+
for (const [k, v] of Object.entries(CORS_HEADERS))
|
|
142
|
+
res.setHeader(k, v);
|
|
143
|
+
res.writeHead(200);
|
|
144
|
+
res.end();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const baseUrl = resolveBaseUrl();
|
|
148
|
+
const url = new URL(req.url ?? '/', baseUrl);
|
|
149
|
+
const path = url.pathname;
|
|
150
|
+
const oauthOn = isOAuthConfigured();
|
|
151
|
+
if (path === '/health') {
|
|
152
|
+
sendJson(res, 200, { status: 'ok', server: SERVER_NAME, version: SERVER_VERSION });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// OAuth discovery + endpoints. Only advertised when configured, so a
|
|
156
|
+
// deployment without MCP_OAUTH_SECRET behaves exactly as before (raw PAT).
|
|
157
|
+
if (oauthOn) {
|
|
158
|
+
if (path.startsWith('/.well-known/oauth-protected-resource')) {
|
|
159
|
+
handleProtectedResourceMetadata(res, baseUrl);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (path.startsWith('/.well-known/oauth-authorization-server')) {
|
|
163
|
+
handleAuthorizationServerMetadata(res, baseUrl);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (path === '/register') {
|
|
167
|
+
if (req.method !== 'POST') {
|
|
168
|
+
sendJson(res, 405, { error: 'invalid_request', error_description: 'Use POST to register.' });
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
await handleRegister(req, res);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (path === '/authorize') {
|
|
175
|
+
if (req.method !== 'GET' && req.method !== 'POST') {
|
|
176
|
+
sendJson(res, 405, { error: 'invalid_request', error_description: 'Use GET or POST.' });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const query = {};
|
|
180
|
+
for (const [k, v] of url.searchParams)
|
|
181
|
+
query[k] = v;
|
|
182
|
+
await handleAuthorize(req, res, query);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (path === '/token') {
|
|
186
|
+
if (req.method !== 'POST') {
|
|
187
|
+
sendJson(res, 405, { error: 'invalid_request', error_description: 'Use POST for the token endpoint.' });
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
await handleToken(req, res);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (path === '/mcp' || path === '/') {
|
|
195
|
+
if (req.method === 'GET' || req.method === 'DELETE') {
|
|
196
|
+
sendJson(res, 405, jsonRpcError(null, -32000, 'Method not allowed. Use POST for MCP requests.'));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
await handleMcp(req, res, baseUrl);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
sendJson(res, 404, jsonRpcError(null, -32601, `Not found: ${path}`));
|
|
203
|
+
}
|
|
144
204
|
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -14,8 +14,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
14
14
|
import { DainOsClient } from './client.js';
|
|
15
15
|
import { allTools } from './tools/index.js';
|
|
16
16
|
import { registerToolHandlers } from './server-setup.js';
|
|
17
|
-
|
|
18
|
-
const PACKAGE_VERSION = '0.6.0';
|
|
17
|
+
import { SERVER_NAME, SERVER_VERSION, SERVER_INSTRUCTIONS } from './meta.js';
|
|
19
18
|
function fail(message) {
|
|
20
19
|
process.stderr.write(`[dainos-mcp] ${message}\n`);
|
|
21
20
|
process.exit(1);
|
|
@@ -30,7 +29,7 @@ async function main() {
|
|
|
30
29
|
}
|
|
31
30
|
const baseUrl = process.env.DAINOS_API_URL;
|
|
32
31
|
const client = new DainOsClient(baseUrl, token);
|
|
33
|
-
const server = new Server({ name:
|
|
32
|
+
const server = new Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS });
|
|
34
33
|
registerToolHandlers(server, client);
|
|
35
34
|
const transport = new StdioServerTransport();
|
|
36
35
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAE7E,SAAS,IAAI,CAAC,OAAe;IAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,OAAO,IAAI,CAAC,CAAC;IAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,IAAI,CACF,0IAA0I,CAC3I,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QACnC,IAAI,CACF,8FAA8F,CAC/F,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC3C,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAEhD,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,EAC9C,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,mBAAmB,EAAE,CACnE,CAAC;IAEF,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAErC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2BAA2B,QAAQ,CAAC,MAAM,eAAe,MAAM,CAAC,OAAO,KAAK,CAC7E,CAAC;AACJ,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,IAAI,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC"}
|
package/dist/meta.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server metadata shared by both transports (stdio `src/index.ts` and
|
|
3
|
+
* HTTP `src/http.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Keep SERVER_VERSION in lock-step with package.json `version` on every
|
|
6
|
+
* release — the publish-mcp-server flow bumps both. Previously each entry
|
|
7
|
+
* point hardcoded its own version constant (both stuck at '0.6.0' while the
|
|
8
|
+
* package was on 0.11.0), so clients were told the wrong version. Centralising
|
|
9
|
+
* it here removes that drift between the two transports.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SERVER_NAME = "dainos";
|
|
12
|
+
export declare const SERVER_VERSION = "0.13.0";
|
|
13
|
+
/**
|
|
14
|
+
* Instructions advertised to MCP clients in the `initialize` response.
|
|
15
|
+
*
|
|
16
|
+
* Why this exists: v0.8 (#405) collapsed ~36 per-entity CRUD tools into the
|
|
17
|
+
* generic describe_schema / query / mutate trio. Commands and skills in the
|
|
18
|
+
* DainOS instruction library that predate that refactor still NAME the removed
|
|
19
|
+
* tools (e.g. list_tenant_users, lookup_product_repos, log_session_context).
|
|
20
|
+
* A client that loads such an instruction would otherwise try to call a tool
|
|
21
|
+
* that no longer exists and stall. This block is the standing translation
|
|
22
|
+
* layer: every MCP client receives it at connect time, regardless of which
|
|
23
|
+
* instruction it later runs, so stale tool names stay recoverable until the
|
|
24
|
+
* library catches up.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SERVER_INSTRUCTIONS = "# DainOS MCP\n\nDainOS is the agency operating system (projects, tasks, proposals, CRM,\ndeveloper changelog/sessions/knowledge-base, and the Claude instruction\nlibrary). This server exposes it through two kinds of tools.\n\n## 1. Generic tools \u2014 operate on ANY resource\n\n- describe_schema \u2014 list every resource with its fields, filters, and allowed\n operations. CALL THIS FIRST in a session before reading or writing data you\n are not already certain about.\n- query \u2014 read. { resource, id? | filters? | search?, parentId?, sort?, limit?, offset? }\n- mutate \u2014 write. { resource, operation: 'create' | 'update' | 'delete', id?, parentId?, data? }\n\nNested resources need parentId (e.g. milestones under a project, comments under\na task). Free-text search only works on resources marked searchable.\n\n## 2. Semantic tools \u2014 carry business logic the generic trio can't express\n\nPrefer the semantic tool whenever one matches your intent:\n\n Tasks create_task \u00B7 complete_task \u00B7 regenerate_client_view\n Cadence claim_next_task \u00B7 report_task_progress \u00B7 mark_blocked \u00B7 request_unblock\n Proposals set_proposal_status \u00B7 create_proposal_option \u00B7 update_proposal_option\n set_recommended_option \u00B7 proposal_content \u00B7 describe_proposal_block_types\n Developer log_changelog_entry \u00B7 log_knowledge_base_entry \u00B7 log_time_entry \u00B7 score_pr_finding\n Library get_instruction \u00B7 list_instructions \u00B7 search_instructions \u00B7 upsert_instruction\n\nThese are all CURRENT. Do not translate them.\n\n## Old tool names (read this if an instruction names a tool you don't have)\n\nv0.8 removed ~36 thin CRUD tools. If a command, skill, or rule tells you to call\na tool that is NOT listed above, it predates the refactor. Translate it:\n\n list_projects / get_project -> query projects\n list_tasks / get_task -> query tasks\n list_milestones -> query milestones (parentId = projectId)\n update_task -> mutate tasks update\n add_task_comment -> mutate task_comments create (parentId = taskId)\n list_proposals / get_proposal -> query proposals\n list_recent_sessions / list_sessions -> query session_context\n log_session_context -> mutate session_context create\n list_changelog -> query developer_changelog\n search_knowledge_base -> query developer_knowledge_base (search = \"...\")\n update_knowledge_base_entry -> mutate developer_knowledge_base update\n list_tenant_users / list_users -> query iam_users (filters: status)\n get_user -> query iam_users (id = \"...\")\n lookup_product_repos -> query product_repos (filters: owner, repo)\n summarise_product_tasks -> query product_task_summary (parentId = productId)\n list_unscored_pr_findings -> query pr_review_findings (filters: repo, prNumber, scoredBy)\n\ncomplete_task still exists for the task done-state walk \u2014 only the plain field\nupdate moved to mutate. When in doubt, call describe_schema and match your\nintent to a resource.";
|
|
27
|
+
//# sourceMappingURL=meta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../src/meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,WAAW,CAAC;AACpC,eAAO,MAAM,cAAc,WAAW,CAAC;AAEvC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mBAAmB,stGAsDV,CAAC"}
|
package/dist/meta.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server metadata shared by both transports (stdio `src/index.ts` and
|
|
3
|
+
* HTTP `src/http.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Keep SERVER_VERSION in lock-step with package.json `version` on every
|
|
6
|
+
* release — the publish-mcp-server flow bumps both. Previously each entry
|
|
7
|
+
* point hardcoded its own version constant (both stuck at '0.6.0' while the
|
|
8
|
+
* package was on 0.11.0), so clients were told the wrong version. Centralising
|
|
9
|
+
* it here removes that drift between the two transports.
|
|
10
|
+
*/
|
|
11
|
+
export const SERVER_NAME = 'dainos';
|
|
12
|
+
export const SERVER_VERSION = '0.13.0';
|
|
13
|
+
/**
|
|
14
|
+
* Instructions advertised to MCP clients in the `initialize` response.
|
|
15
|
+
*
|
|
16
|
+
* Why this exists: v0.8 (#405) collapsed ~36 per-entity CRUD tools into the
|
|
17
|
+
* generic describe_schema / query / mutate trio. Commands and skills in the
|
|
18
|
+
* DainOS instruction library that predate that refactor still NAME the removed
|
|
19
|
+
* tools (e.g. list_tenant_users, lookup_product_repos, log_session_context).
|
|
20
|
+
* A client that loads such an instruction would otherwise try to call a tool
|
|
21
|
+
* that no longer exists and stall. This block is the standing translation
|
|
22
|
+
* layer: every MCP client receives it at connect time, regardless of which
|
|
23
|
+
* instruction it later runs, so stale tool names stay recoverable until the
|
|
24
|
+
* library catches up.
|
|
25
|
+
*/
|
|
26
|
+
export const SERVER_INSTRUCTIONS = `# DainOS MCP
|
|
27
|
+
|
|
28
|
+
DainOS is the agency operating system (projects, tasks, proposals, CRM,
|
|
29
|
+
developer changelog/sessions/knowledge-base, and the Claude instruction
|
|
30
|
+
library). This server exposes it through two kinds of tools.
|
|
31
|
+
|
|
32
|
+
## 1. Generic tools — operate on ANY resource
|
|
33
|
+
|
|
34
|
+
- describe_schema — list every resource with its fields, filters, and allowed
|
|
35
|
+
operations. CALL THIS FIRST in a session before reading or writing data you
|
|
36
|
+
are not already certain about.
|
|
37
|
+
- query — read. { resource, id? | filters? | search?, parentId?, sort?, limit?, offset? }
|
|
38
|
+
- mutate — write. { resource, operation: 'create' | 'update' | 'delete', id?, parentId?, data? }
|
|
39
|
+
|
|
40
|
+
Nested resources need parentId (e.g. milestones under a project, comments under
|
|
41
|
+
a task). Free-text search only works on resources marked searchable.
|
|
42
|
+
|
|
43
|
+
## 2. Semantic tools — carry business logic the generic trio can't express
|
|
44
|
+
|
|
45
|
+
Prefer the semantic tool whenever one matches your intent:
|
|
46
|
+
|
|
47
|
+
Tasks create_task · complete_task · regenerate_client_view
|
|
48
|
+
Cadence claim_next_task · report_task_progress · mark_blocked · request_unblock
|
|
49
|
+
Proposals set_proposal_status · create_proposal_option · update_proposal_option
|
|
50
|
+
set_recommended_option · proposal_content · describe_proposal_block_types
|
|
51
|
+
Developer log_changelog_entry · log_knowledge_base_entry · log_time_entry · score_pr_finding
|
|
52
|
+
Library get_instruction · list_instructions · search_instructions · upsert_instruction
|
|
53
|
+
|
|
54
|
+
These are all CURRENT. Do not translate them.
|
|
55
|
+
|
|
56
|
+
## Old tool names (read this if an instruction names a tool you don't have)
|
|
57
|
+
|
|
58
|
+
v0.8 removed ~36 thin CRUD tools. If a command, skill, or rule tells you to call
|
|
59
|
+
a tool that is NOT listed above, it predates the refactor. Translate it:
|
|
60
|
+
|
|
61
|
+
list_projects / get_project -> query projects
|
|
62
|
+
list_tasks / get_task -> query tasks
|
|
63
|
+
list_milestones -> query milestones (parentId = projectId)
|
|
64
|
+
update_task -> mutate tasks update
|
|
65
|
+
add_task_comment -> mutate task_comments create (parentId = taskId)
|
|
66
|
+
list_proposals / get_proposal -> query proposals
|
|
67
|
+
list_recent_sessions / list_sessions -> query session_context
|
|
68
|
+
log_session_context -> mutate session_context create
|
|
69
|
+
list_changelog -> query developer_changelog
|
|
70
|
+
search_knowledge_base -> query developer_knowledge_base (search = "...")
|
|
71
|
+
update_knowledge_base_entry -> mutate developer_knowledge_base update
|
|
72
|
+
list_tenant_users / list_users -> query iam_users (filters: status)
|
|
73
|
+
get_user -> query iam_users (id = "...")
|
|
74
|
+
lookup_product_repos -> query product_repos (filters: owner, repo)
|
|
75
|
+
summarise_product_tasks -> query product_task_summary (parentId = productId)
|
|
76
|
+
list_unscored_pr_findings -> query pr_review_findings (filters: repo, prNumber, scoredBy)
|
|
77
|
+
|
|
78
|
+
complete_task still exists for the task done-state walk — only the plain field
|
|
79
|
+
update moved to mutate. When in doubt, call describe_schema and match your
|
|
80
|
+
intent to a resource.`;
|
|
81
|
+
//# sourceMappingURL=meta.js.map
|
package/dist/meta.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../src/meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC;AACpC,MAAM,CAAC,MAAM,cAAc,GAAG,QAAQ,CAAC;AAEvC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sBAsDb,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the MCP server's embedded OAuth 2.1 authorization server.
|
|
3
|
+
*
|
|
4
|
+
* The cloud endpoint (mcp.dainos.app) is reachable from Claude in the browser,
|
|
5
|
+
* which only speaks OAuth — it has no field for a static Bearer token. So this
|
|
6
|
+
* server acts as a minimal, STATELESS OAuth authorization server that bridges
|
|
7
|
+
* the browser's OAuth flow onto the existing DainOS Personal Access Token (PAT)
|
|
8
|
+
* model:
|
|
9
|
+
*
|
|
10
|
+
* - The user pastes a `dain_pat_...` token on the consent page (`/authorize`).
|
|
11
|
+
* - We issue an OAuth authorization code, then access/refresh tokens, each of
|
|
12
|
+
* which is an ENCRYPTED JWT (JWE) that embeds the PAT.
|
|
13
|
+
* - On `/mcp`, we decrypt the access token to recover the PAT and call
|
|
14
|
+
* api.dainos.app exactly as the Claude Code (raw-PAT) path does today.
|
|
15
|
+
*
|
|
16
|
+
* Statelessness is deliberate: the Vercel function has no datastore. Every
|
|
17
|
+
* token carries its own state, signed + encrypted under MCP_OAUTH_SECRET.
|
|
18
|
+
*/
|
|
19
|
+
/** Env var holding the canonical public origin, e.g. `https://mcp.dainos.app`. */
|
|
20
|
+
export declare const PUBLIC_URL_ENV = "MCP_PUBLIC_URL";
|
|
21
|
+
export declare const DEFAULT_PUBLIC_URL = "https://mcp.dainos.app";
|
|
22
|
+
/** Env var holding the symmetric secret used to encrypt every issued token. */
|
|
23
|
+
export declare const OAUTH_SECRET_ENV = "MCP_OAUTH_SECRET";
|
|
24
|
+
/** Token lifetimes (seconds). */
|
|
25
|
+
export declare const ACCESS_TOKEN_TTL_SECONDS: number;
|
|
26
|
+
export declare const REFRESH_TOKEN_TTL_SECONDS: number;
|
|
27
|
+
export declare const AUTH_CODE_TTL_SECONDS = 30;
|
|
28
|
+
export declare const CLIENT_REG_TTL_SECONDS: number;
|
|
29
|
+
/** The single scope this server understands. */
|
|
30
|
+
export declare const SCOPE = "mcp";
|
|
31
|
+
/** The DainOS PAT prefix. Kept in lock-step with the API's `PAT_PREFIX`. */
|
|
32
|
+
export declare const PAT_PREFIX = "dain_pat_";
|
|
33
|
+
/** Where users mint a PAT. Surfaced on the consent page. */
|
|
34
|
+
export declare const PAT_SETTINGS_URL = "https://dainos.app/settings/api-tokens";
|
|
35
|
+
/**
|
|
36
|
+
* Resolve this server's public base URL.
|
|
37
|
+
*
|
|
38
|
+
* Pinned to the configured canonical origin (MCP_PUBLIC_URL, default
|
|
39
|
+
* mcp.dainos.app). The inbound `Host` header is deliberately NOT trusted: it is
|
|
40
|
+
* attacker-controllable (a direct request to the raw function URL can carry any
|
|
41
|
+
* Host), and letting it set the issuer / authorization_endpoint / token_endpoint
|
|
42
|
+
* in the discovery documents (and the resource_metadata in the WWW-Authenticate
|
|
43
|
+
* challenge) would let a spoofed Host point an OAuth client's token fetch at an
|
|
44
|
+
* attacker-controlled endpoint. The connector always targets the canonical URL,
|
|
45
|
+
* so pinning is also correct, not just safe.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveBaseUrl(): string;
|
|
48
|
+
/**
|
|
49
|
+
* Derive a 32-byte AES key from MCP_OAUTH_SECRET via SHA-256.
|
|
50
|
+
*
|
|
51
|
+
* @throws if the secret is missing or too short. The caller turns this into a
|
|
52
|
+
* clear 500/oauth-disabled response rather than leaking a stack trace.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getEncryptionKey(): Uint8Array;
|
|
55
|
+
/** True when the OAuth layer is configured and able to issue/verify tokens. */
|
|
56
|
+
export declare function isOAuthConfigured(): boolean;
|
|
57
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/oauth/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,kFAAkF;AAClF,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAC/C,eAAO,MAAM,kBAAkB,2BAA2B,CAAC;AAE3D,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,iCAAiC;AACjC,eAAO,MAAM,wBAAwB,QAAU,CAAC;AAChD,eAAO,MAAM,yBAAyB,QAAoB,CAAC;AAK3D,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,eAAO,MAAM,sBAAsB,QAAqB,CAAC;AAEzD,gDAAgD;AAChD,eAAO,MAAM,KAAK,QAAQ,CAAC;AAE3B,4EAA4E;AAC5E,eAAO,MAAM,UAAU,cAAc,CAAC;AAEtC,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB,2CAA2C,CAAC;AAEzE;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CAS7C;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,IAAI,OAAO,CAG3C"}
|