@nooticr/mcp 1.26.48 → 1.26.49

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 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;
@@ -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
+ }
@@ -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
- constructor(baseUrl, tokenProvider, idempotencyKey) {
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
  }
@@ -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.48";
33
+ export const MCP_SERVER_VERSION = "1.26.49";
34
34
  /** MCP Apps extension identifier */
35
35
  const UI_EXTENSION = "io.modelcontextprotocol/ui";
36
36
  /** MIME type for MCP Apps HTML resources */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nooticr/mcp",
3
3
  "mcpName": "com.nooticr/mcp",
4
- "version": "1.26.48",
4
+ "version": "1.26.49",
5
5
  "description": "MCP server for nooticr - fetch, discover and understand TikTok/Instagram/YouTube/X/Reddit/LinkedIn/Weibo posts with AI (media metadata, niche discovery, brand monitoring, hook & viral analysis)",
6
6
  "homepage": "https://mcp.nooticr.com",
7
7
  "repository": {