@nooticr/mcp 1.26.48 → 1.26.50
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/index.js +9 -1
- package/dist/shared/api-key.js +38 -0
- package/dist/shared/nooticr.js +5 -2
- package/dist/shared/output-schemas.js +1 -0
- package/dist/shared/tools.js +9 -2
- package/dist/shared/ui-template.js +1899 -1520
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
15
15
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
16
16
|
import { getBaseUrl, getPublicUrl, getPort, getCredentialsFile, getTransportMode, DEFAULT_PORT, } from "./config.js";
|
|
17
17
|
import { AuthManager, NooticrAuthError, createApiKeyTokenProvider, createHttpTokenProvider, createManagementTokenProvider, createStdioTokenProvider, } from "./auth.js";
|
|
18
|
-
import { looksLikeApiKey } from "./shared/api-key.js";
|
|
18
|
+
import { forwardedHeaders, forwardedSession, looksLikeApiKey } from "./shared/api-key.js";
|
|
19
19
|
import { NooticrClient, NooticrError } from "./nooticr.js";
|
|
20
20
|
import { OAuthManager, SCOPES } from "./oauth.js";
|
|
21
21
|
import { createMcpServer } from "./shared/tools.js";
|
|
@@ -95,6 +95,9 @@ function validMcpToken(token, oauth) {
|
|
|
95
95
|
return true;
|
|
96
96
|
if (looksLikeApiKey(token))
|
|
97
97
|
return true;
|
|
98
|
+
// Same reasoning as a key: the backend verifies the session on every call.
|
|
99
|
+
if (forwardedSession(token))
|
|
100
|
+
return true;
|
|
98
101
|
const envToken = process.env.NOOTICR_ACCESS_TOKEN;
|
|
99
102
|
return typeof envToken === "string" && envToken.length > 0 && token === envToken;
|
|
100
103
|
}
|
|
@@ -242,6 +245,11 @@ export async function runHttp(port, publicUrl) {
|
|
|
242
245
|
if (looksLikeApiKey(bearer)) {
|
|
243
246
|
return new NooticrClient(baseUrl, createApiKeyTokenProvider(bearer));
|
|
244
247
|
}
|
|
248
|
+
// nooticr-server's chat, calling as the user signed in to it.
|
|
249
|
+
const forwarded = forwardedSession(bearer);
|
|
250
|
+
if (forwarded) {
|
|
251
|
+
return new NooticrClient(baseUrl, createApiKeyTokenProvider(forwarded.token), undefined, forwardedHeaders(forwarded));
|
|
252
|
+
}
|
|
245
253
|
const session = bearer
|
|
246
254
|
? oauth.verifyToken(bearer)
|
|
247
255
|
: undefined;
|
package/dist/shared/api-key.js
CHANGED
|
@@ -18,3 +18,41 @@ export const API_KEY_PREFIX = "nk_";
|
|
|
18
18
|
export function looksLikeApiKey(token) {
|
|
19
19
|
return typeof token === "string" && token.startsWith(API_KEY_PREFIX);
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* A nooticr session forwarded by nooticr-server's own agent — the dashboard's
|
|
23
|
+
* chat, calling these tools on behalf of the user who is signed in there.
|
|
24
|
+
*
|
|
25
|
+
* The chat used to call a second, Rust copy of 28 of these tools, so it had
|
|
26
|
+
* none of the others and none of their guidance. It now calls this server,
|
|
27
|
+
* and needs a way to say "as this user" that is not an OAuth grant (there is
|
|
28
|
+
* no browser in the loop) and not an API key (the user never minted one).
|
|
29
|
+
*
|
|
30
|
+
* Shape: `nooticr-session.<conversation id or ->.<nooticr access token>`. The
|
|
31
|
+
* token is the user's own, forwarded to the backend exactly as a key is, and
|
|
32
|
+
* the backend checks it on every call. The conversation rides along so the
|
|
33
|
+
* backend's run ledger files each call under the chat thread that made it,
|
|
34
|
+
* rather than as an anonymous connector call. The prefix keeps it from ever
|
|
35
|
+
* being read as an OAuth token this process issued, which a bare JWT would be.
|
|
36
|
+
*/
|
|
37
|
+
export const SESSION_TOKEN_PREFIX = "nooticr-session.";
|
|
38
|
+
/** The session inside a forwarded bearer, or undefined for anything else. */
|
|
39
|
+
export function forwardedSession(bearer) {
|
|
40
|
+
if (typeof bearer !== "string" || !bearer.startsWith(SESSION_TOKEN_PREFIX))
|
|
41
|
+
return undefined;
|
|
42
|
+
const rest = bearer.slice(SESSION_TOKEN_PREFIX.length);
|
|
43
|
+
const dot = rest.indexOf(".");
|
|
44
|
+
if (dot < 0)
|
|
45
|
+
return undefined;
|
|
46
|
+
const conversation = rest.slice(0, dot);
|
|
47
|
+
const token = rest.slice(dot + 1).trim();
|
|
48
|
+
if (!token)
|
|
49
|
+
return undefined;
|
|
50
|
+
return /^[0-9a-f-]{36}$/i.test(conversation) ? { token, conversationId: conversation } : { token };
|
|
51
|
+
}
|
|
52
|
+
/** Headers that attribute a forwarded call to the chat thread that made it. */
|
|
53
|
+
export function forwardedHeaders(session) {
|
|
54
|
+
return {
|
|
55
|
+
"x-nooticr-surface": "chat",
|
|
56
|
+
...(session.conversationId ? { "x-nooticr-conversation": session.conversationId } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
package/dist/shared/nooticr.js
CHANGED
|
@@ -45,14 +45,17 @@ export class NooticrClient {
|
|
|
45
45
|
tokenProvider;
|
|
46
46
|
/** Stable per-call key so a retried tool call is billed once. */
|
|
47
47
|
idempotencyKey;
|
|
48
|
-
|
|
48
|
+
/** Sent on every request — how a forwarded chat call is attributed. */
|
|
49
|
+
extraHeaders;
|
|
50
|
+
constructor(baseUrl, tokenProvider, idempotencyKey, extraHeaders = {}) {
|
|
49
51
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
50
52
|
this.tokenProvider = tokenProvider;
|
|
51
53
|
this.idempotencyKey = idempotencyKey;
|
|
54
|
+
this.extraHeaders = extraHeaders;
|
|
52
55
|
}
|
|
53
56
|
async request(method, path, opts = {}) {
|
|
54
57
|
const doRequest = async (accessToken) => {
|
|
55
|
-
const headers = {};
|
|
58
|
+
const headers = { ...this.extraHeaders };
|
|
56
59
|
if (opts.body !== undefined) {
|
|
57
60
|
headers["content-type"] = "application/json";
|
|
58
61
|
}
|
|
@@ -851,6 +851,7 @@ export const OUTPUT_SCHEMAS = {
|
|
|
851
851
|
mcpCredits,
|
|
852
852
|
}),
|
|
853
853
|
get_post_transcript: open({
|
|
854
|
+
url: scalar().describe("The post the transcript came from, as asked for."),
|
|
854
855
|
available: scalar().describe("false when the post carries no caption track."),
|
|
855
856
|
// Deliberately no text: it is already in `transcript`, and a caption
|
|
856
857
|
// track is long enough that sending it twice would be a real cost in a
|
package/dist/shared/tools.js
CHANGED
|
@@ -30,7 +30,7 @@ import { registerCollabTools } from "./collab.js";
|
|
|
30
30
|
import { registerAmazonTools } from "./amazon.js";
|
|
31
31
|
import { registerMarketplaceTools } from "./marketplace.js";
|
|
32
32
|
/** Current MCP server version — bumped on every deploy for traceability. */
|
|
33
|
-
export const MCP_SERVER_VERSION = "1.26.
|
|
33
|
+
export const MCP_SERVER_VERSION = "1.26.50";
|
|
34
34
|
/** MCP Apps extension identifier */
|
|
35
35
|
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
36
36
|
/** MIME type for MCP Apps HTML resources */
|
|
@@ -1302,7 +1302,14 @@ export function createMcpServer(rawMakeClient, opts) {
|
|
|
1302
1302
|
}, async (args, extra) => {
|
|
1303
1303
|
const client = await makeClient({ ...extra, arguments: args });
|
|
1304
1304
|
try {
|
|
1305
|
-
|
|
1305
|
+
const res = await client.callTool("get_post_transcript", { ...args });
|
|
1306
|
+
// The post the words came from, echoed so the view can open a video at
|
|
1307
|
+
// the moment a cue was said — the backend's result names the words, not
|
|
1308
|
+
// the post, and a timestamp you cannot follow is only a number.
|
|
1309
|
+
if (res.structured && typeof res.structured === "object" && !("url" in res.structured)) {
|
|
1310
|
+
res.structured = { ...res.structured, url: args.url };
|
|
1311
|
+
}
|
|
1312
|
+
return await toToolResult(res);
|
|
1306
1313
|
}
|
|
1307
1314
|
catch (err) {
|
|
1308
1315
|
return toolError("get_post_transcript failed", err);
|