@mambalabsdev/mcp-github-organization-signal-scanner 1.0.0 → 1.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.
Files changed (3) hide show
  1. package/README.md +142 -0
  2. package/build/index.js +138 -49
  3. package/package.json +5 -4
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # GitHub Organization Signal Scanner MCP Server
2
+
3
+ [![Smithery](https://smithery.ai/badge/mambabuilt/mcp-github-organization-signal-scanner)](https://smithery.ai/servers/mambabuilt/mcp-github-organization-signal-scanner) [![Glama score](https://glama.ai/mcp/servers/mambalabsdev/mcp-github-organization-signal-scanner/badges/score.svg)](https://glama.ai/mcp/servers/mambalabsdev/mcp-github-organization-signal-scanner) [![MCP Registry](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fregistry.modelcontextprotocol.io%2Fv0%2Fservers%3Fsearch%3Dcom.mambabuilt%252Fmcp-github-organization-signal-scanner%26limit%3D1&query=%24.servers%5B0%5D._meta%5B%22io.modelcontextprotocol.registry%2Fofficial%22%5D.status&label=mcp%20registry&color=blue)](https://registry.modelcontextprotocol.io/v0/servers?search=com.mambabuilt/mcp-github-organization-signal-scanner&limit=1) [![npm version](https://img.shields.io/npm/v/@mambalabsdev/mcp-github-organization-signal-scanner)](https://www.npmjs.com/package/@mambalabsdev/mcp-github-organization-signal-scanner) [![npm downloads](https://img.shields.io/npm/dm/@mambalabsdev/mcp-github-organization-signal-scanner)](https://www.npmjs.com/package/@mambalabsdev/mcp-github-organization-signal-scanner) [![license](https://img.shields.io/github/license/mambalabsdev/mcp-github-organization-signal-scanner)](https://github.com/mambalabsdev/mcp-github-organization-signal-scanner/blob/main/LICENSE) [![mcpservers.org](https://img.shields.io/badge/mcpservers.org-listed-blue)](https://mcpservers.org/servers/mambalabsdev/mcp-github-organization-signal-scanner)
4
+
5
+ An MCP server that resolves a company domain to its GitHub organization with repo, language and activity signals. It wraps the Mamba Labs GitHub Organization Signal Scanner actor on Apify and returns a Clay-ready flat JSON row to any MCP client.
6
+
7
+ ## What's Inside
8
+
9
+ - [What it does](#what-it-does)
10
+ - [Quick start](#quick-start)
11
+ - [Prerequisites](#prerequisites)
12
+ - [Example prompts](#example-prompts)
13
+ - [Inputs](#inputs)
14
+ - [Output](#output)
15
+ - [Example output](#example-output)
16
+ - [Features](#features)
17
+ - [Full actor documentation](#full-actor-documentation)
18
+ - [Mamba Labs GTM Suite](#mamba-labs-gtm-suite)
19
+ - [License](#license)
20
+
21
+ ## What it does
22
+
23
+ Give it a company domain and it finds that company's GitHub organization and returns repository count, followers, creation date, top languages, total stars, the most recent push, how many repositories are actively worked on, and whether the company ships a public SDK. One flat row per company, read through GitHub's own documented REST API.
24
+
25
+ Forks and archived repositories are excluded from the language and star derivations, and the exclusion is counted on the row. A rate limited request reports `not_extractable` and never a zero, because a zero is a number a buyer would filter on. A login that resolves to a personal user account rather than an organization is reported as `identity_mismatch`. It reads organization and repository metadata only: it does not clone repositories, read source, scan for secrets or assess code quality.
26
+
27
+ All of the scanning runs on Apify. This package is a thin client that calls the actor and hands back the result unchanged.
28
+
29
+ ## Quick start
30
+
31
+ You need Node.js 18 or newer and an Apify account with an API token.
32
+
33
+ Add this to your Claude Desktop config:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "mamba-github-organization-signal-scanner": {
39
+ "command": "npx",
40
+ "args": ["-y", "@mambalabsdev/mcp-github-organization-signal-scanner"],
41
+ "env": {
42
+ "APIFY_TOKEN": "your-apify-token"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ Get your token at https://console.apify.com/account/integrations, paste it in, and restart Claude Desktop. The `scan_github_organization_signals` tool will be available.
50
+
51
+ ## Prerequisites
52
+
53
+ - Node.js 18 or newer
54
+ - An Apify account with an API token
55
+ - Optional: your own GitHub personal access token, free to create at github.com/settings/tokens with no scopes at all for public data, if you want to scan more than a handful of companies
56
+
57
+ ## Example prompts
58
+
59
+ - "Find the GitHub organization for stripe.com and summarize its activity."
60
+ - "Scan the GitHub org `vercel` and tell me its top languages and total stars."
61
+ - "Does figma.com ship a public SDK? Check its GitHub organization."
62
+ - "Scan notion.so's GitHub org, counting a repo as active if it was pushed in the last 30 days."
63
+
64
+ ## Inputs
65
+
66
+ - `company_domain` (optional): bare company domain, for example `stripe.com`. This is the lookup key and the join key for every other actor in the fleet.
67
+ - `company_name` (optional but strongly recommended): what the identity gate checks a discovered record against, so supplying it is the cheapest way to reduce wrong matches.
68
+ - `github_org` (optional): if you already know the organization login, for example `stripe`, put it here and the tool skips discovery entirely and goes straight to the API, which is faster and spends fewer rate limited requests.
69
+ - `includeRepoDetail` (optional): when true (the default) the organization repositories are read and the language, star, activity and SDK signals are derived from them. Set false to return the organization record only, which is one request instead of several and is friendlier to the unauthenticated rate limit.
70
+ - `includeContributorEstimate` (optional): when true one extra request reads the contributor count of the organization's most starred repository, as a floor on the size of its public engineering surface. False by default, because one extra request per company is real money against a 60 per hour unauthenticated budget.
71
+ - `activeWindowDays` (optional): how recently a repository must have been pushed to count as active. One of `30`, `90`, `180` or `365`, with 90 the default. This changes what active means on the row.
72
+ - `repoPageBudget` (optional): how many pages of 100 repositories to read for a large organization. One of `1`, `2`, `3` or `5`. This is a cost and completeness dial, not a change of answer: the row always reports how many repositories were sampled and whether the sample is complete.
73
+ - `githubToken` (optional): your own GitHub personal access token, free to create with no scopes at all for public data. Without it the tool runs at GitHub's 60 requests per hour, which is enough for a handful of companies and not enough for a list. With it the limit is 5,000 per hour.
74
+ - `skipCache` (optional): when false (the default) a successful lookup is cached for seven days and reused. Set true to force a fresh fetch.
75
+
76
+ ## Output
77
+
78
+ The tool returns the actor's flat JSON row for the company, with 32 snake_case fields and no nested objects. `github_discovery` says how the organization was found, `github_rejected_candidate` records a candidate the identity gate turned away, `repos_sampled` and `repos_complete` say how much of the organization was read, and `repos_excluded` counts the forks and archived repositories left out. See the Apify Store page for the full output schema.
79
+
80
+ ## Example output
81
+
82
+ ```json
83
+ {
84
+ "degraded": false,
85
+ "degradation_reason": null,
86
+ "company_domain": "stripe.com",
87
+ "company_name": "Stripe",
88
+ "github_org": "stripe",
89
+ "github_url": "https://github.com/stripe",
90
+ "github_discovery": "homepage_link",
91
+ "github_rejected_candidate": null,
92
+ "github_account_type": "Organization",
93
+ "public_repos": 98,
94
+ "followers": 3469,
95
+ "org_created_at": "2011-06-17T15:42:37Z",
96
+ "org_location": "San Francisco, CA",
97
+ "top_languages": "TypeScript, Go, Ruby, JavaScript, HTML",
98
+ "total_stars": 42821,
99
+ "most_starred_repo": "stripe-node",
100
+ "most_starred_repo_stars": 4485,
101
+ "most_recent_push_at": "2026-08-22T18:33:15.000Z",
102
+ "active_repos_90d": 56,
103
+ "has_public_sdk": true,
104
+ "repos_sampled": 98,
105
+ "repos_complete": true,
106
+ "repos_excluded": "forks=6, archived=5",
107
+ "rate_limit_remaining": 57,
108
+ "coverage": 1,
109
+ "fetch_status": "ok",
110
+ "run_date": "2026-08-22T19:23:50.177Z"
111
+ }
112
+ ```
113
+
114
+ ## Features
115
+
116
+ - Resolves a GitHub organization starting from a company domain
117
+ - Repository count, stars, followers and top languages
118
+ - Recent push activity and repositories active in a window you choose
119
+ - Public SDK detection, a direct developer tooling signal
120
+ - Runs keyless, with your own GitHub token available for scale
121
+ - Rejected candidates recorded in `github_rejected_candidate`
122
+ - 32 flat snake_case fields, one row per company
123
+
124
+ ## Full actor documentation
125
+
126
+ This server is a thin client and holds no scanning logic. For the complete input and output reference, pricing, and run history, see the Apify Store page:
127
+
128
+ https://apify.com/mambalabs/github-organization-signal-scanner
129
+
130
+ ---
131
+
132
+ ## Mamba Labs GTM Suite
133
+
134
+ This server is one of the Mamba Labs GTM Suite MCP servers. Every actor in the suite takes a domain or a company and returns one flat row, so they stack in the same Clay table without reshaping anything. The actor behind this server is the GitHub Organization Signal Scanner, immutable Apify actor ID `ahF8RRpv5mzPao5CN`.
135
+
136
+ > Built by [Mamba Labs](https://github.com/mambalabsdev) | [npm](https://www.npmjs.com/org/mambalabsdev) | [Apify Store](https://apify.com/mambalabs)
137
+
138
+ ## License
139
+
140
+ MIT
141
+
142
+ Built by Mamba Labs. https://apify.com/mambalabs
package/build/index.js CHANGED
@@ -9,7 +9,6 @@ const here = dirname(fileURLToPath(import.meta.url));
9
9
  const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
10
10
  // Distinctive UA so Apify run meta.userAgent marks MCP-originated runs.
11
11
  const USER_AGENT = `mambalabs-mcp ${pkg.name}@${pkg.version}`;
12
- const APIFY_TOKEN = process.env.APIFY_TOKEN;
13
12
  // Drop undefined values so optional inputs are not sent to the actor at all.
14
13
  function compact(obj) {
15
14
  const out = {};
@@ -26,36 +25,46 @@ function compact(obj) {
26
25
  function boolToString(v) {
27
26
  return v === undefined ? undefined : v ? "true" : "false";
28
27
  }
29
- // actorPath is the actor's IMMUTABLE Apify actor id, not its slug, so a Store
30
- // rename never breaks these calls.
28
+ // How long the actor run itself is allowed to take, in seconds. One value for
29
+ // every Mamba Labs wrapper, set 2026-10-05: start and poll exists so a long run
30
+ // survives, and a shorter limit would end the long runs it was built for. Past
31
+ // this limit the run ends TIMED-OUT and the caller is told so, with the run id.
32
+ const ACTOR_RUN_TIMEOUT_SECS = 1800;
33
+ // memory=512 matches the actor's declared defaultRunOptions.memoryMbytes and
34
+ // is pinned so the run starts at the size the actor asks for: `apify-actor-start`
35
+ // bills once per GB with a minimum of one. Keep this in step with the actor.
36
+ const ACTOR_RUN_MEMORY_MBYTES = 512;
37
+ // How long this wrapper waits for that run, in milliseconds. The actor's own
38
+ // timeout plus two minutes, so the run's own TIMED-OUT status is what the
39
+ // caller sees rather than the wrapper giving up first and reporting nothing.
40
+ const WRAPPER_WAIT_MS = (ACTOR_RUN_TIMEOUT_SECS + 120) * 1000;
41
+ const POLL_INTERVAL_MS = 3000;
42
+ const TERMINAL = new Set(["SUCCEEDED", "FAILED", "TIMED-OUT", "ABORTED", "ABORTING"]);
43
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
44
+ // Shared caller. actorPath is the actor's immutable Apify actor ID (a stable key
45
+ // that survives Store renames). The /v2/acts/{id} endpoint accepts it directly,
46
+ // so a Store rename never breaks these calls.
47
+ //
48
+ // START AND POLL, NOT RUN-SYNC. Apify's synchronous endpoints carry a platform
49
+ // ceiling of 300 seconds on the HTTP wait itself and answer 408 past it whatever
50
+ // the timeout parameter says, so a long run reads as a timeout even though the
51
+ // actor goes on to finish. Starting the run, polling it to a terminal status and
52
+ // then reading the dataset is the only way to wait as long as the actor needs.
53
+ //
54
+ // The token is read here rather than at module load, so the tool registers
55
+ // unconditionally and a server started without APIFY_TOKEN still advertises its
56
+ // capabilities instead of reporting none.
31
57
  async function runActor(actorPath, actorLabel, input) {
58
+ const APIFY_TOKEN = process.env.APIFY_TOKEN;
32
59
  if (!APIFY_TOKEN) {
33
60
  return { isError: true, content: [{ type: "text", text: "APIFY_TOKEN is not set. Create a token at https://console.apify.com/account/integrations and set it as the APIFY_TOKEN environment variable." }] };
34
61
  }
35
- // memory=512 is deliberate and matches the actor's declared
36
- // defaultRunOptions.memoryMbytes. run-sync-get-dataset-items runs at 2048 MB
37
- // unless told otherwise, and `apify-actor-start` bills once per GB with a
38
- // minimum of one, so leaving the default in place would charge the caller
39
- // more start events per run than the actor asks for. Keep this in step with
40
- // the actor's defaultRunOptions.
41
- const url = `https://api.apify.com/v2/acts/${actorPath}/run-sync-get-dataset-items?timeout=300&memory=512`;
42
- let response;
43
- try {
44
- response = await fetch(url, {
45
- method: "POST",
46
- headers: {
47
- Authorization: `Bearer ${APIFY_TOKEN}`,
48
- "Content-Type": "application/json",
49
- "User-Agent": USER_AGENT,
50
- },
51
- body: JSON.stringify(input),
52
- });
53
- }
54
- catch (err) {
55
- const message = err instanceof Error ? err.message : String(err);
56
- return { isError: true, content: [{ type: "text", text: `Could not reach the Apify API: ${message}` }] };
57
- }
58
- if (!response.ok) {
62
+ const headers = {
63
+ Authorization: `Bearer ${APIFY_TOKEN}`,
64
+ "Content-Type": "application/json",
65
+ "User-Agent": USER_AGENT,
66
+ };
67
+ const httpError = async (response) => {
59
68
  let detail = "";
60
69
  try {
61
70
  const body = (await response.json());
@@ -65,27 +74,107 @@ async function runActor(actorPath, actorLabel, input) {
65
74
  catch {
66
75
  detail = "";
67
76
  }
68
- let message;
69
77
  switch (response.status) {
78
+ case 400:
79
+ return `The ${actorLabel} run was rejected as invalid input.${detail}`;
70
80
  case 401:
71
- message = "Invalid Apify token. Check your APIFY_TOKEN environment variable.";
72
- break;
81
+ return "Invalid Apify token. Check your APIFY_TOKEN environment variable.";
73
82
  case 402:
74
- message = "Insufficient Apify credits. Check your account balance at https://console.apify.com/billing";
75
- break;
76
- case 408:
77
- message = `The ${actorLabel} run timed out after 300 seconds. Try again, or run the actor on Apify directly for longer jobs.`;
78
- break;
83
+ return "Insufficient Apify credits. Check your account balance at https://console.apify.com/billing";
79
84
  default:
80
- message = `Apify request to ${actorLabel} failed with status ${response.status}.${detail}`;
85
+ return `Apify request to ${actorLabel} failed with status ${response.status}.${detail}`;
86
+ }
87
+ };
88
+ // 1. Start the run.
89
+ let started;
90
+ try {
91
+ started = await fetch(`https://api.apify.com/v2/acts/${actorPath}/runs?timeout=${ACTOR_RUN_TIMEOUT_SECS}&memory=${ACTOR_RUN_MEMORY_MBYTES}`, { method: "POST", headers, body: JSON.stringify(input) });
92
+ }
93
+ catch (err) {
94
+ const message = err instanceof Error ? err.message : String(err);
95
+ return { isError: true, content: [{ type: "text", text: `Could not reach the Apify API: ${message}` }] };
96
+ }
97
+ if (!started.ok) {
98
+ return { isError: true, content: [{ type: "text", text: await httpError(started) }] };
99
+ }
100
+ let run;
101
+ try {
102
+ run = (await started.json()).data ?? {};
103
+ }
104
+ catch (err) {
105
+ const message = err instanceof Error ? err.message : String(err);
106
+ return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run start returned a response that could not be parsed: ${message}` }] };
107
+ }
108
+ const runId = run.id;
109
+ if (!runId) {
110
+ return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run start returned no run id, so there is nothing to wait for.` }] };
111
+ }
112
+ // 2. Poll to a terminal status.
113
+ const deadline = Date.now() + WRAPPER_WAIT_MS;
114
+ let status = run.status ?? "READY";
115
+ let datasetId = run.defaultDatasetId;
116
+ while (!TERMINAL.has(status)) {
117
+ if (Date.now() >= deadline) {
118
+ return {
119
+ isError: true,
120
+ content: [{ type: "text", text: `The ${actorLabel} run ${runId} was still ${status} after ${Math.round(WRAPPER_WAIT_MS / 1000)} seconds and this call stopped waiting. The run itself is still on Apify: read it at https://console.apify.com/actors/runs/${runId}` }],
121
+ };
122
+ }
123
+ await sleep(POLL_INTERVAL_MS);
124
+ let poll;
125
+ try {
126
+ poll = await fetch(`https://api.apify.com/v2/actor-runs/${runId}`, { headers });
127
+ }
128
+ catch (err) {
129
+ const message = err instanceof Error ? err.message : String(err);
130
+ return { isError: true, content: [{ type: "text", text: `Lost contact with the Apify API while waiting for ${actorLabel} run ${runId}: ${message}` }] };
131
+ }
132
+ if (!poll.ok) {
133
+ return { isError: true, content: [{ type: "text", text: await httpError(poll) }] };
81
134
  }
82
- return { isError: true, content: [{ type: "text", text: message }] };
135
+ const body = (await poll.json());
136
+ status = body.data?.status ?? status;
137
+ datasetId = body.data?.defaultDatasetId ?? datasetId;
138
+ }
139
+ // 3. A run that did not succeed is a failure the caller must see, never an
140
+ // empty success. Surfacing it here is what keeps a crashed run from reading
141
+ // as "no results found".
142
+ if (status !== "SUCCEEDED") {
143
+ return {
144
+ isError: true,
145
+ content: [{ type: "text", text: `The ${actorLabel} run did not succeed (run ID: ${runId}, status: ${status}).` }],
146
+ };
147
+ }
148
+ if (!datasetId) {
149
+ return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run ${runId} succeeded but reported no dataset, so there is nothing to return.` }] };
150
+ }
151
+ // 4. Read the dataset.
152
+ let ds;
153
+ try {
154
+ ds = await fetch(`https://api.apify.com/v2/datasets/${datasetId}/items?format=json`, { headers });
155
+ }
156
+ catch (err) {
157
+ const message = err instanceof Error ? err.message : String(err);
158
+ return { isError: true, content: [{ type: "text", text: `Could not read the ${actorLabel} dataset: ${message}` }] };
159
+ }
160
+ if (!ds.ok) {
161
+ return { isError: true, content: [{ type: "text", text: await httpError(ds) }] };
162
+ }
163
+ let items;
164
+ try {
165
+ items = await ds.json();
166
+ }
167
+ catch (err) {
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run returned a response that could not be parsed: ${message}` }] };
170
+ }
171
+ if (!Array.isArray(items)) {
172
+ const asObj = items;
173
+ const detail = asObj?.error?.message
174
+ ? `${asObj.error.message}`
175
+ : JSON.stringify(items);
176
+ return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run did not return a dataset. ${detail}` }] };
83
177
  }
84
- // A 2xx normally carries the dataset array. Pass actor output through
85
- // unchanged: the wrapper must never reinterpret a status field, because
86
- // not_extractable, blocked and not_found are different answers and collapsing
87
- // them is exactly the defect the actor was built to avoid.
88
- const items = await response.json();
89
178
  return { content: [{ type: "text", text: JSON.stringify(items, null, 2) }] };
90
179
  }
91
180
  const server = new McpServer({
@@ -115,22 +204,22 @@ server.registerTool("scan_github_organization_signals", {
115
204
  .describe("Optional. If you already know the organization login, for example \"stripe\", put it here and the actor skips discovery entirely and goes straight to the API, which is faster and spends fewer of your rate limited requests."),
116
205
  includeRepoDetail: z.boolean()
117
206
  .optional()
118
- .describe("When \"true\" (default) the organization repositories are read and the language, star, activity and SDK signals are derived from them. Set \"false\" to return the organization record only, which is one request instead of several and is much friendlier to the unauthenticated rate limit. Sent as a string for Clay compatibility."),
207
+ .describe("When true (default) the organization repositories are read and the language, star, activity and SDK signals are derived from them. Set false to return the organization record only, which is one request instead of several and is much friendlier to the unauthenticated rate limit."),
119
208
  includeContributorEstimate: z.boolean()
120
209
  .optional()
121
- .describe("When \"true\" the actor spends one extra request to read the contributor count of the organization most starred repository, as a floor on the size of its public engineering surface. Default \"false\", because one extra request per company is real money against a 60 per hour unauthenticated budget. Sent as a string for Clay compatibility."),
210
+ .describe("When true the actor spends one extra request to read the contributor count of the organization most starred repository, as a floor on the size of its public engineering surface. Default false, because one extra request per company is real money against a 60 per hour unauthenticated budget."),
122
211
  activeWindowDays: z.enum(["30", "90", "180", "365"])
123
212
  .optional()
124
- .describe("How recently a repository must have been pushed to count as active. 90 days by default. This changes what \"active\" means on the row, so pick the window your own definition of an engaged engineering team uses. Sent as a string for Clay compatibility."),
213
+ .describe("How recently a repository must have been pushed to count as active. 90 days by default. This changes what \"active\" means on the row, so pick the window your own definition of an engaged engineering team uses."),
125
214
  repoPageBudget: z.enum(["1", "2", "3", "5"])
126
215
  .optional()
127
- .describe("How many pages of 100 repositories to read for a large organization. This is a cost and completeness dial, not a change of answer: the row always reports how many repositories were actually sampled and whether the sample is complete. Sent as a string for Clay compatibility."),
216
+ .describe("How many pages of 100 repositories to read for a large organization. This is a cost and completeness dial, not a change of answer: the row always reports how many repositories were actually sampled and whether the sample is complete."),
128
217
  githubToken: z.string()
129
218
  .optional()
130
- .describe("YOUR OWN GitHub personal access token, free to create at github.com/settings/tokens with no scopes at all for public data. OPTIONAL: without it the actor runs at GitHub 60 requests per hour, which is enough for a handful of companies and not enough for a list. With it the limit is 5,000 per hour. It is marked secret, so the value never renders on this page."),
219
+ .describe("YOUR OWN GitHub personal access token, free to create at github.com/settings/tokens with no scopes at all for public data. OPTIONAL: without it the actor runs at GitHub 60 requests per hour, which is enough for a handful of companies and not enough for a list. With it the limit is 5,000 per hour. A token passed here travels in the tool call and stays in the chat transcript, so prefer setting GITHUB_TOKEN in this server's environment: when this argument is omitted the server sends that variable instead."),
131
220
  skipCache: z.boolean()
132
221
  .optional()
133
- .describe("When \"false\" (default) a successful lookup is cached for seven days and reused, which costs you nothing on a repeated run. Set \"true\" to force a fresh fetch. Sent as a string for Clay compatibility."),
222
+ .describe("When false (default) a successful lookup is cached for seven days and reused, which costs you nothing on a repeated run. Set true to force a fresh fetch."),
134
223
  },
135
224
  }, async ({ company_domain, company_name, github_org, includeRepoDetail, includeContributorEstimate, activeWindowDays, repoPageBudget, githubToken, skipCache }) => {
136
225
  return runActor("ahF8RRpv5mzPao5CN", "GitHub Organization Signal Scanner", compact({
@@ -141,7 +230,7 @@ server.registerTool("scan_github_organization_signals", {
141
230
  includeContributorEstimate: boolToString(includeContributorEstimate),
142
231
  activeWindowDays,
143
232
  repoPageBudget,
144
- githubToken,
233
+ githubToken: githubToken ?? process.env.GITHUB_TOKEN,
145
234
  skipCache: boolToString(skipCache),
146
235
  }));
147
236
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mambalabsdev/mcp-github-organization-signal-scanner",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "MCP server for the Mamba Labs GitHub Organization Signal Scanner actor on Apify. Resolve a company domain to its GitHub organization with repo, language and activity signals.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "build": "tsc && chmod +x build/index.js",
17
+ "test": "npm run build && node --test test/*.test.js",
17
18
  "prepublishOnly": "npm run build"
18
19
  },
19
20
  "keywords": [
@@ -38,11 +39,11 @@
38
39
  },
39
40
  "dependencies": {
40
41
  "@modelcontextprotocol/sdk": "^1.12.0",
41
- "zod": "^3.23.8"
42
+ "zod": "^4.5.4"
42
43
  },
43
44
  "devDependencies": {
44
- "@types/node": "^20.14.0",
45
- "typescript": "^5.6.0"
45
+ "@types/node": "^26.4.1",
46
+ "typescript": "5.9.3"
46
47
  },
47
48
  "mcpName": "com.mambabuilt/mcp-github-organization-signal-scanner"
48
49
  }