@hasna/mcps 0.0.15 → 0.0.17

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
@@ -9492,7 +9492,7 @@ var init_provider_profile_seeds = __esm(() => {
9492
9492
  provenance: {
9493
9493
  source: "curated",
9494
9494
  sourceUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
9495
- verifiedAt: "2026-05-10"
9495
+ verifiedAt: "2026-05-11"
9496
9496
  }
9497
9497
  },
9498
9498
  {
@@ -9524,7 +9524,384 @@ var init_provider_profile_seeds = __esm(() => {
9524
9524
  provenance: {
9525
9525
  source: "curated",
9526
9526
  sourceUrl: "https://linear.app/docs/mcp",
9527
- verifiedAt: "2026-05-10"
9527
+ verifiedAt: "2026-05-11"
9528
+ }
9529
+ },
9530
+ {
9531
+ id: "github",
9532
+ displayName: "GitHub",
9533
+ description: "Connect GitHub so agents can inspect repositories, manage issues and pull requests, analyze Actions runs, and work with project metadata.",
9534
+ endpoint: "https://api.githubcopilot.com/mcp/",
9535
+ transport: "streamable-http",
9536
+ authType: "oauth2",
9537
+ authMetadata: {
9538
+ oauthVersion: "2.0",
9539
+ pkce: true,
9540
+ dynamicClientRegistration: false,
9541
+ bearerToken: "optional",
9542
+ notes: "GitHub hosts the remote server. OAuth requires host-side GitHub App/OAuth App setup; clients that cannot complete OAuth can use an Authorization bearer GitHub PAT."
9543
+ },
9544
+ scopes: ["repo", "read:org", "read:user", "user:email", "workflow", "notifications", "project"],
9545
+ tokenMode: "user",
9546
+ installFallback: {
9547
+ command: "docker",
9548
+ args: ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"],
9549
+ env: { GITHUB_PERSONAL_ACCESS_TOKEN: "GITHUB_PERSONAL_ACCESS_TOKEN" },
9550
+ packageName: "ghcr.io/github/github-mcp-server",
9551
+ url: "https://api.githubcopilot.com/mcp/"
9552
+ },
9553
+ docsUrl: "https://github.com/github/github-mcp-server",
9554
+ safety: {
9555
+ requiresApproval: true,
9556
+ sensitiveScopes: ["repo", "workflow", "project"],
9557
+ dataClasses: ["repositories", "issues", "pull_requests", "actions", "security_alerts", "organization_members"],
9558
+ notes: "GitHub tools can read private source and mutate repository objects. Prefer least-privilege OAuth scopes or fine-grained PATs and approval-gate write operations."
9559
+ },
9560
+ provenance: {
9561
+ source: "curated",
9562
+ sourceUrl: "https://github.com/github/github-mcp-server",
9563
+ repositoryUrl: "https://github.com/github/github-mcp-server",
9564
+ packageName: "ghcr.io/github/github-mcp-server",
9565
+ verifiedAt: "2026-05-11"
9566
+ }
9567
+ },
9568
+ {
9569
+ id: "slack",
9570
+ displayName: "Slack",
9571
+ description: "Connect Slack so agents can search workspace messages and files, inspect conversations, read user context, and draft or send approved messages.",
9572
+ endpoint: "https://mcp.slack.com/mcp",
9573
+ transport: "streamable-http",
9574
+ authType: "oauth2",
9575
+ authMetadata: {
9576
+ oauthVersion: "2.0",
9577
+ pkce: true,
9578
+ dynamicClientRegistration: false,
9579
+ bearerToken: "none",
9580
+ notes: "Slack's remote MCP endpoint uses Streamable HTTP, requires a registered Slack app identity, and does not support SSE or dynamic client registration."
9581
+ },
9582
+ scopes: [
9583
+ "search:read.public",
9584
+ "search:read.private",
9585
+ "search:read.mpim",
9586
+ "search:read.im",
9587
+ "search:read.files",
9588
+ "search:read.users",
9589
+ "channels:history",
9590
+ "groups:history",
9591
+ "mpim:history",
9592
+ "im:history",
9593
+ "chat:write",
9594
+ "canvases:read",
9595
+ "canvases:write",
9596
+ "users:read",
9597
+ "users:read.email"
9598
+ ],
9599
+ tokenMode: "user",
9600
+ docsUrl: "https://docs.slack.dev/ai/slack-mcp-server/",
9601
+ safety: {
9602
+ requiresApproval: true,
9603
+ sensitiveScopes: ["chat:write", "canvases:write", "users:read.email"],
9604
+ dataClasses: ["messages", "files", "channels", "users", "canvases"],
9605
+ notes: "Slack data often includes confidential team communication. Search/read access should stay tenant-scoped and message/canvas writes should require explicit approval."
9606
+ },
9607
+ provenance: {
9608
+ source: "curated",
9609
+ sourceUrl: "https://docs.slack.dev/ai/slack-mcp-server/",
9610
+ verifiedAt: "2026-05-11"
9611
+ }
9612
+ },
9613
+ {
9614
+ id: "gmail",
9615
+ displayName: "Gmail",
9616
+ description: "Connect Gmail through the open Workspace MCP server for mail search, message retrieval, drafts, labels, filters, and approved send workflows.",
9617
+ transport: "stdio",
9618
+ authType: "oauth2",
9619
+ authMetadata: {
9620
+ oauthVersion: "2.1",
9621
+ pkce: true,
9622
+ dynamicClientRegistration: false,
9623
+ bearerToken: "optional",
9624
+ notes: "Workspace MCP is an independent open-source Google Workspace server. Configure your own Google OAuth client and storage backend for Gmail access."
9625
+ },
9626
+ scopes: [
9627
+ "https://www.googleapis.com/auth/gmail.readonly",
9628
+ "https://www.googleapis.com/auth/gmail.modify",
9629
+ "https://www.googleapis.com/auth/gmail.compose",
9630
+ "https://www.googleapis.com/auth/gmail.send"
9631
+ ],
9632
+ tokenMode: "user",
9633
+ installFallback: {
9634
+ command: "uvx",
9635
+ args: ["workspace-mcp"],
9636
+ env: { WORKSPACE_MCP_PERMISSIONS: "gmail:send" },
9637
+ packageName: "workspace-mcp",
9638
+ url: "http://127.0.0.1:8000/mcp"
9639
+ },
9640
+ docsUrl: "https://workspacemcp.com/docs",
9641
+ safety: {
9642
+ requiresApproval: true,
9643
+ sensitiveScopes: [
9644
+ "https://www.googleapis.com/auth/gmail.modify",
9645
+ "https://www.googleapis.com/auth/gmail.compose",
9646
+ "https://www.googleapis.com/auth/gmail.send"
9647
+ ],
9648
+ dataClasses: ["email_messages", "threads", "attachments", "labels", "contacts"],
9649
+ notes: "Email content and sending are high-impact actions. Prefer read-only permissions until a user intentionally enables drafts or sends."
9650
+ },
9651
+ provenance: {
9652
+ source: "github",
9653
+ sourceUrl: "https://workspacemcp.com/docs",
9654
+ repositoryUrl: "https://github.com/taylorwilsdon/google_workspace_mcp",
9655
+ packageName: "workspace-mcp",
9656
+ verifiedAt: "2026-05-11"
9657
+ }
9658
+ },
9659
+ {
9660
+ id: "google-drive",
9661
+ displayName: "Google Drive",
9662
+ description: "Connect Google Drive through the open Workspace MCP server for file search, folder navigation, content reads, and approved file operations.",
9663
+ transport: "stdio",
9664
+ authType: "oauth2",
9665
+ authMetadata: {
9666
+ oauthVersion: "2.1",
9667
+ pkce: true,
9668
+ dynamicClientRegistration: false,
9669
+ bearerToken: "optional",
9670
+ notes: "Workspace MCP is an independent open-source Google Workspace server. Configure your own Google OAuth client and storage backend for Drive access."
9671
+ },
9672
+ scopes: [
9673
+ "https://www.googleapis.com/auth/drive.readonly",
9674
+ "https://www.googleapis.com/auth/drive.file",
9675
+ "https://www.googleapis.com/auth/drive"
9676
+ ],
9677
+ tokenMode: "user",
9678
+ installFallback: {
9679
+ command: "uvx",
9680
+ args: ["workspace-mcp"],
9681
+ env: { WORKSPACE_MCP_PERMISSIONS: "drive:readonly" },
9682
+ packageName: "workspace-mcp",
9683
+ url: "http://127.0.0.1:8000/mcp"
9684
+ },
9685
+ docsUrl: "https://workspacemcp.com/docs",
9686
+ safety: {
9687
+ requiresApproval: true,
9688
+ sensitiveScopes: [
9689
+ "https://www.googleapis.com/auth/drive.file",
9690
+ "https://www.googleapis.com/auth/drive"
9691
+ ],
9692
+ dataClasses: ["drive_files", "folders", "documents", "spreadsheets", "attachments", "sharing_permissions"],
9693
+ notes: "Drive access can expose broad business documents. Keep tenant allowlists and approval-gate creation, movement, and permission changes."
9694
+ },
9695
+ provenance: {
9696
+ source: "github",
9697
+ sourceUrl: "https://workspacemcp.com/docs",
9698
+ repositoryUrl: "https://github.com/taylorwilsdon/google_workspace_mcp",
9699
+ packageName: "workspace-mcp",
9700
+ verifiedAt: "2026-05-11"
9701
+ }
9702
+ },
9703
+ {
9704
+ id: "google-calendar",
9705
+ displayName: "Google Calendar",
9706
+ description: "Connect Google Calendar through the open Workspace MCP server for calendar discovery, event reads, and approved scheduling changes.",
9707
+ transport: "stdio",
9708
+ authType: "oauth2",
9709
+ authMetadata: {
9710
+ oauthVersion: "2.1",
9711
+ pkce: true,
9712
+ dynamicClientRegistration: false,
9713
+ bearerToken: "optional",
9714
+ notes: "Workspace MCP is an independent open-source Google Workspace server. Configure your own Google OAuth client and storage backend for Calendar access."
9715
+ },
9716
+ scopes: [
9717
+ "https://www.googleapis.com/auth/calendar.readonly",
9718
+ "https://www.googleapis.com/auth/calendar.events",
9719
+ "https://www.googleapis.com/auth/calendar"
9720
+ ],
9721
+ tokenMode: "user",
9722
+ installFallback: {
9723
+ command: "uvx",
9724
+ args: ["workspace-mcp"],
9725
+ env: { WORKSPACE_MCP_PERMISSIONS: "calendar:readonly" },
9726
+ packageName: "workspace-mcp",
9727
+ url: "http://127.0.0.1:8000/mcp"
9728
+ },
9729
+ docsUrl: "https://workspacemcp.com/docs",
9730
+ safety: {
9731
+ requiresApproval: true,
9732
+ sensitiveScopes: [
9733
+ "https://www.googleapis.com/auth/calendar.events",
9734
+ "https://www.googleapis.com/auth/calendar"
9735
+ ],
9736
+ dataClasses: ["calendars", "events", "attendees", "attachments", "availability"],
9737
+ notes: "Calendar writes can invite people, reveal availability, and alter operational schedules. Gate creates, updates, and deletes with user approval."
9738
+ },
9739
+ provenance: {
9740
+ source: "github",
9741
+ sourceUrl: "https://workspacemcp.com/docs",
9742
+ repositoryUrl: "https://github.com/taylorwilsdon/google_workspace_mcp",
9743
+ packageName: "workspace-mcp",
9744
+ verifiedAt: "2026-05-11"
9745
+ }
9746
+ },
9747
+ {
9748
+ id: "stripe",
9749
+ displayName: "Stripe",
9750
+ description: "Connect Stripe so agents can inspect accounts, balances, customers, prices, subscriptions, invoices, disputes, and perform approved Stripe API operations.",
9751
+ endpoint: "https://mcp.stripe.com",
9752
+ transport: "streamable-http",
9753
+ authType: "oauth2",
9754
+ authMetadata: {
9755
+ oauthVersion: "2.0",
9756
+ pkce: true,
9757
+ dynamicClientRegistration: false,
9758
+ bearerToken: "optional",
9759
+ notes: "Stripe supports OAuth MCP sessions and restricted API keys as Authorization bearer tokens for clients that do not support OAuth."
9760
+ },
9761
+ tokenMode: "workspace",
9762
+ installFallback: {
9763
+ command: "npx",
9764
+ args: ["-y", "@stripe/mcp"],
9765
+ env: { STRIPE_SECRET_KEY: "STRIPE_SECRET_KEY" },
9766
+ packageName: "@stripe/mcp",
9767
+ url: "https://mcp.stripe.com"
9768
+ },
9769
+ docsUrl: "https://docs.stripe.com/mcp",
9770
+ safety: {
9771
+ requiresApproval: true,
9772
+ sensitiveScopes: ["restricted_api_key", "live_mode"],
9773
+ dataClasses: ["payments", "customers", "subscriptions", "invoices", "disputes", "account_balances"],
9774
+ notes: "Use restricted keys, separate sandbox/live mode, and require approval for any operation that creates, updates, refunds, cancels, or exposes customer data."
9775
+ },
9776
+ provenance: {
9777
+ source: "curated",
9778
+ sourceUrl: "https://docs.stripe.com/mcp",
9779
+ repositoryUrl: "https://github.com/stripe/ai",
9780
+ packageName: "@stripe/mcp",
9781
+ verifiedAt: "2026-05-11"
9782
+ }
9783
+ },
9784
+ {
9785
+ id: "cloudflare",
9786
+ displayName: "Cloudflare",
9787
+ description: "Connect Cloudflare's remote MCP server for account, zone, developer platform, logs, analytics, and approved infrastructure operations.",
9788
+ endpoint: "https://mcp.cloudflare.com/mcp",
9789
+ transport: "streamable-http",
9790
+ authType: "oauth2",
9791
+ authMetadata: {
9792
+ oauthVersion: "2.0",
9793
+ pkce: true,
9794
+ dynamicClientRegistration: false,
9795
+ bearerToken: "none",
9796
+ notes: "Cloudflare's remote MCP server redirects users through Cloudflare authorization and permission selection."
9797
+ },
9798
+ tokenMode: "workspace",
9799
+ docsUrl: "https://developers.cloudflare.com/agents/model-context-protocol/mcp-servers-for-cloudflare/",
9800
+ safety: {
9801
+ requiresApproval: true,
9802
+ sensitiveScopes: ["account_admin", "zone_write", "workers_write", "dns_write"],
9803
+ dataClasses: ["accounts", "zones", "dns_records", "workers", "logs", "analytics", "security_events"],
9804
+ notes: "Cloudflare tools can affect live infrastructure. Require account scoping, least-privilege permissions, and approval for write operations."
9805
+ },
9806
+ provenance: {
9807
+ source: "curated",
9808
+ sourceUrl: "https://github.com/cloudflare/mcp",
9809
+ repositoryUrl: "https://github.com/cloudflare/mcp",
9810
+ verifiedAt: "2026-05-11"
9811
+ }
9812
+ },
9813
+ {
9814
+ id: "postgres",
9815
+ displayName: "PostgreSQL",
9816
+ description: "Connect a PostgreSQL database through the reference read-only MCP server for schema inspection and SQL query analysis.",
9817
+ transport: "stdio",
9818
+ authType: "api_key",
9819
+ authMetadata: {
9820
+ bearerToken: "none",
9821
+ notes: "The stdio server takes a PostgreSQL connection URL argument. Store the URL in a secret manager or environment variable and grant read-only database credentials."
9822
+ },
9823
+ tokenMode: "service",
9824
+ installFallback: {
9825
+ command: "npx",
9826
+ args: ["-y", "@modelcontextprotocol/server-postgres", "${POSTGRES_URL}"],
9827
+ packageName: "@modelcontextprotocol/server-postgres"
9828
+ },
9829
+ docsUrl: "https://www.npmjs.com/package/@modelcontextprotocol/server-postgres",
9830
+ safety: {
9831
+ readOnly: true,
9832
+ requiresApproval: false,
9833
+ sensitiveScopes: ["database_connection_url"],
9834
+ dataClasses: ["database_schema", "table_rows", "query_results"],
9835
+ notes: "Use read-only database users and network allowlists. Treat query results as sensitive even when the server enforces read-only transactions."
9836
+ },
9837
+ provenance: {
9838
+ source: "npm",
9839
+ sourceUrl: "https://www.npmjs.com/package/@modelcontextprotocol/server-postgres",
9840
+ repositoryUrl: "https://github.com/modelcontextprotocol/servers",
9841
+ packageName: "@modelcontextprotocol/server-postgres",
9842
+ verifiedAt: "2026-05-11"
9843
+ }
9844
+ },
9845
+ {
9846
+ id: "filesystem",
9847
+ displayName: "Filesystem",
9848
+ description: "Connect the reference filesystem MCP server for scoped local file and directory reads, writes, searches, metadata, and move operations.",
9849
+ transport: "stdio",
9850
+ authType: "none",
9851
+ authMetadata: {
9852
+ bearerToken: "none",
9853
+ notes: "Filesystem access is bounded by explicit root directories passed to the local stdio server."
9854
+ },
9855
+ tokenMode: "none",
9856
+ installFallback: {
9857
+ command: "npx",
9858
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "${workspaceFolder}"],
9859
+ packageName: "@modelcontextprotocol/server-filesystem"
9860
+ },
9861
+ docsUrl: "https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem",
9862
+ safety: {
9863
+ requiresApproval: true,
9864
+ destructiveTools: ["write_file", "delete_file", "move_file", "create_directory"],
9865
+ dataClasses: ["local_files", "source_code", "documents", "directory_metadata"],
9866
+ notes: "Always constrain roots to intended project directories and approval-gate write, delete, and move operations."
9867
+ },
9868
+ provenance: {
9869
+ source: "npm",
9870
+ sourceUrl: "https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem",
9871
+ repositoryUrl: "https://github.com/modelcontextprotocol/servers",
9872
+ packageName: "@modelcontextprotocol/server-filesystem",
9873
+ verifiedAt: "2026-05-11"
9874
+ }
9875
+ },
9876
+ {
9877
+ id: "browser",
9878
+ displayName: "Browser Automation",
9879
+ description: "Connect Playwright MCP so agents can navigate pages, inspect accessibility snapshots, fill forms, capture screenshots, and run browser automation.",
9880
+ transport: "stdio",
9881
+ authType: "none",
9882
+ authMetadata: {
9883
+ bearerToken: "none",
9884
+ notes: "Playwright MCP runs browser automation locally and may reuse browser profile state unless configured for isolation."
9885
+ },
9886
+ tokenMode: "none",
9887
+ installFallback: {
9888
+ command: "npx",
9889
+ args: ["-y", "@playwright/mcp"],
9890
+ packageName: "@playwright/mcp"
9891
+ },
9892
+ docsUrl: "https://playwright.dev/docs/getting-started-mcp",
9893
+ safety: {
9894
+ requiresApproval: true,
9895
+ destructiveTools: ["browser_click", "browser_type", "browser_file_upload", "browser_run_code"],
9896
+ dataClasses: ["web_pages", "forms", "cookies", "local_storage", "screenshots", "browser_profiles"],
9897
+ notes: "Browser automation can submit forms, alter accounts, and expose logged-in sessions. Prefer isolated profiles and require approval for side-effecting actions."
9898
+ },
9899
+ provenance: {
9900
+ source: "curated",
9901
+ sourceUrl: "https://playwright.dev/docs/getting-started-mcp",
9902
+ repositoryUrl: "https://github.com/microsoft/playwright-mcp",
9903
+ packageName: "@playwright/mcp",
9904
+ verifiedAt: "2026-05-11"
9528
9905
  }
9529
9906
  }
9530
9907
  ];
@@ -9547,6 +9924,7 @@ function getDb() {
9547
9924
  command TEXT NOT NULL,
9548
9925
  args TEXT NOT NULL DEFAULT '[]',
9549
9926
  env TEXT NOT NULL DEFAULT '{}',
9927
+ credential_refs TEXT NOT NULL DEFAULT '{}',
9550
9928
  transport TEXT NOT NULL DEFAULT 'stdio',
9551
9929
  url TEXT,
9552
9930
  source TEXT NOT NULL DEFAULT 'local',
@@ -9573,6 +9951,9 @@ function getDb() {
9573
9951
  try {
9574
9952
  db.exec("ALTER TABLE servers ADD COLUMN last_error TEXT");
9575
9953
  } catch {}
9954
+ try {
9955
+ db.exec("ALTER TABLE servers ADD COLUMN credential_refs TEXT NOT NULL DEFAULT '{}'");
9956
+ } catch {}
9576
9957
  db.exec(`
9577
9958
  CREATE TABLE IF NOT EXISTS sources (
9578
9959
  id TEXT PRIMARY KEY,
@@ -9643,22 +10024,19 @@ function getDb() {
9643
10024
  db.exec("ALTER TABLE provider_profiles ADD COLUMN auth_metadata TEXT NOT NULL DEFAULT '{}'");
9644
10025
  } catch {}
9645
10026
  db.exec("CREATE INDEX IF NOT EXISTS idx_provider_profiles_enabled ON provider_profiles(enabled)");
9646
- const providerProfileCount = db.query("SELECT COUNT(*) as c FROM provider_profiles").get().c;
9647
- if (providerProfileCount === 0) {
9648
- const insertProviderProfile = db.prepare(`
9649
- INSERT OR IGNORE INTO provider_profiles (
9650
- id, display_name, description, endpoint, transport, fallback_endpoints,
9651
- auth_type, auth_metadata, scopes, token_mode, install_fallback,
9652
- docs_url, safety, provenance, enabled
9653
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
9654
- `);
9655
- const run = db.transaction(() => {
9656
- for (const profile of DEFAULT_PROVIDER_PROFILE_SEEDS) {
9657
- insertProviderProfile.run(profile.id, profile.displayName, profile.description ?? null, profile.endpoint ?? null, profile.transport, JSON.stringify(profile.fallbackEndpoints ?? []), profile.authType, JSON.stringify(profile.authMetadata ?? {}), JSON.stringify(profile.scopes ?? []), profile.tokenMode ?? "none", JSON.stringify(profile.installFallback ?? null), profile.docsUrl ?? null, JSON.stringify(profile.safety ?? {}), JSON.stringify(profile.provenance), profile.enabled === false ? 0 : 1);
9658
- }
9659
- });
9660
- run();
9661
- }
10027
+ const insertProviderProfile = db.prepare(`
10028
+ INSERT OR IGNORE INTO provider_profiles (
10029
+ id, display_name, description, endpoint, transport, fallback_endpoints,
10030
+ auth_type, auth_metadata, scopes, token_mode, install_fallback,
10031
+ docs_url, safety, provenance, enabled
10032
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
10033
+ `);
10034
+ const seedProviderProfiles = db.transaction(() => {
10035
+ for (const profile of DEFAULT_PROVIDER_PROFILE_SEEDS) {
10036
+ insertProviderProfile.run(profile.id, profile.displayName, profile.description ?? null, profile.endpoint ?? null, profile.transport, JSON.stringify(profile.fallbackEndpoints ?? []), profile.authType, JSON.stringify(profile.authMetadata ?? {}), JSON.stringify(profile.scopes ?? []), profile.tokenMode ?? "none", JSON.stringify(profile.installFallback ?? null), profile.docsUrl ?? null, JSON.stringify(profile.safety ?? {}), JSON.stringify(profile.provenance), profile.enabled === false ? 0 : 1);
10037
+ }
10038
+ });
10039
+ seedProviderProfiles();
9662
10040
  db.exec(`
9663
10041
  CREATE TABLE IF NOT EXISTS feedback (
9664
10042
  id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
@@ -16627,17 +17005,17 @@ __export(exports_sources, {
16627
17005
  clearCache: () => clearCache,
16628
17006
  addSource: () => addSource
16629
17007
  });
16630
- import { mkdirSync as mkdirSync6, existsSync as existsSync6, readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
16631
- import { join as join7 } from "path";
17008
+ import { mkdirSync as mkdirSync6, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
17009
+ import { join as join8 } from "path";
16632
17010
  function getCacheFile(sourceId) {
16633
- return join7(CACHE_DIR, `${sourceId}.json`);
17011
+ return join8(CACHE_DIR, `${sourceId}.json`);
16634
17012
  }
16635
17013
  function readCache(sourceId) {
16636
17014
  try {
16637
17015
  const file = getCacheFile(sourceId);
16638
- if (!existsSync6(file))
17016
+ if (!existsSync7(file))
16639
17017
  return null;
16640
- const data = JSON.parse(readFileSync2(file, "utf-8"));
17018
+ const data = JSON.parse(readFileSync3(file, "utf-8"));
16641
17019
  return data;
16642
17020
  } catch {
16643
17021
  return null;
@@ -16651,7 +17029,7 @@ function writeCache(sourceId, results) {
16651
17029
  }
16652
17030
  function clearCache(sourceId) {
16653
17031
  try {
16654
- if (!existsSync6(CACHE_DIR))
17032
+ if (!existsSync7(CACHE_DIR))
16655
17033
  return;
16656
17034
  const files = readdirSync3(CACHE_DIR);
16657
17035
  for (const file of files) {
@@ -16659,7 +17037,7 @@ function clearCache(sourceId) {
16659
17037
  continue;
16660
17038
  if (!sourceId || file.startsWith(`${sourceId}.`)) {
16661
17039
  try {
16662
- unlinkSync(join7(CACHE_DIR, file));
17040
+ unlinkSync(join8(CACHE_DIR, file));
16663
17041
  } catch {}
16664
17042
  }
16665
17043
  }
@@ -16905,12 +17283,166 @@ var CACHE_DIR, DEFAULT_TTL_MS;
16905
17283
  var init_sources = __esm(() => {
16906
17284
  init_db();
16907
17285
  init_config2();
16908
- CACHE_DIR = join7(MCPS_DIR, "cache");
17286
+ CACHE_DIR = join8(MCPS_DIR, "cache");
16909
17287
  DEFAULT_TTL_MS = 10 * 60 * 1000;
16910
17288
  });
16911
17289
 
16912
17290
  // src/lib/registry.ts
16913
17291
  init_db();
17292
+
17293
+ // src/lib/credentials.ts
17294
+ init_config2();
17295
+ import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
17296
+ import { join as join7 } from "path";
17297
+
17298
+ class CredentialReferenceError extends Error {
17299
+ constructor(message) {
17300
+ super(message);
17301
+ this.name = "CredentialReferenceError";
17302
+ }
17303
+ }
17304
+ var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
17305
+ var SECRET_VALUE_PATTERN = /^(sk_(?:live|test)_[A-Za-z0-9_]+|ghp_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|xox[baprs]-[A-Za-z0-9-]+|AIza[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/;
17306
+ var REDACTED_CREDENTIAL_VALUE = "<redacted>";
17307
+ function normalizeKey(key) {
17308
+ return key.trim();
17309
+ }
17310
+ function isRecord(value) {
17311
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17312
+ }
17313
+ function isSecretLikeEnvKey(key) {
17314
+ return SECRET_KEY_PATTERN.test(key);
17315
+ }
17316
+ function isSecretLikeValue(value) {
17317
+ return SECRET_VALUE_PATTERN.test(value.trim());
17318
+ }
17319
+ function normalizeCredentialRef(ref) {
17320
+ const source = ref.source;
17321
+ if (source !== "env" && source !== "local-vault" && source !== "hosted") {
17322
+ throw new CredentialReferenceError(`Unsupported credential reference source: ${String(source)}`);
17323
+ }
17324
+ const name = ref.name?.trim();
17325
+ if (!name) {
17326
+ throw new CredentialReferenceError("Credential reference name is required");
17327
+ }
17328
+ return {
17329
+ source,
17330
+ name,
17331
+ required: ref.required !== false,
17332
+ ...ref.description ? { description: ref.description } : {}
17333
+ };
17334
+ }
17335
+ function normalizeCredentialRefs(refs) {
17336
+ const normalized = {};
17337
+ for (const [rawKey, ref] of Object.entries(refs ?? {})) {
17338
+ const key = normalizeKey(rawKey);
17339
+ if (!key)
17340
+ throw new CredentialReferenceError("Credential reference env key is required");
17341
+ normalized[key] = normalizeCredentialRef(ref);
17342
+ }
17343
+ return normalized;
17344
+ }
17345
+ function parseCredentialRefs(value) {
17346
+ if (!isRecord(value))
17347
+ return {};
17348
+ const refs = {};
17349
+ for (const [key, ref] of Object.entries(value)) {
17350
+ if (!isRecord(ref))
17351
+ continue;
17352
+ const source = ref.source;
17353
+ const name = ref.name;
17354
+ if ((source === "env" || source === "local-vault" || source === "hosted") && typeof name === "string" && name.trim()) {
17355
+ refs[key] = normalizeCredentialRef({
17356
+ source,
17357
+ name,
17358
+ required: typeof ref.required === "boolean" ? ref.required : true,
17359
+ description: typeof ref.description === "string" ? ref.description : undefined
17360
+ });
17361
+ }
17362
+ }
17363
+ return refs;
17364
+ }
17365
+ function normalizeLiteralEnv(env) {
17366
+ const normalized = {};
17367
+ for (const [rawKey, rawValue] of Object.entries(env ?? {})) {
17368
+ const key = normalizeKey(rawKey);
17369
+ if (!key)
17370
+ continue;
17371
+ const value = String(rawValue);
17372
+ if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
17373
+ throw new CredentialReferenceError(`Refusing to store raw secret-like env value for "${key}". Use a credential reference instead.`);
17374
+ }
17375
+ normalized[key] = value;
17376
+ }
17377
+ return normalized;
17378
+ }
17379
+ function redactEnv(env) {
17380
+ const redacted = {};
17381
+ for (const [key, value] of Object.entries(env)) {
17382
+ redacted[key] = isSecretLikeEnvKey(key) || isSecretLikeValue(value) ? REDACTED_CREDENTIAL_VALUE : value;
17383
+ }
17384
+ return redacted;
17385
+ }
17386
+ function redactServerCredentials(server) {
17387
+ return {
17388
+ ...server,
17389
+ env: redactEnv(server.env),
17390
+ credentialRefs: normalizeCredentialRefs(server.credentialRefs)
17391
+ };
17392
+ }
17393
+ function readLocalVault() {
17394
+ const path = process.env.HASNA_MCPS_CREDENTIAL_VAULT_PATH ?? join7(MCPS_DIR, "credentials.local.json");
17395
+ if (!existsSync6(path))
17396
+ return {};
17397
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
17398
+ if (!isRecord(parsed))
17399
+ return {};
17400
+ const values = {};
17401
+ for (const [key, value] of Object.entries(parsed)) {
17402
+ if (typeof value === "string")
17403
+ values[key] = value;
17404
+ }
17405
+ return values;
17406
+ }
17407
+ function resolveCredentialRef(envKey, ref) {
17408
+ if (ref.source === "env") {
17409
+ const value = process.env[ref.name];
17410
+ if (value === undefined && ref.required !== false) {
17411
+ throw new CredentialReferenceError(`Missing required environment credential "${ref.name}" for "${envKey}"`);
17412
+ }
17413
+ return value;
17414
+ }
17415
+ if (ref.source === "local-vault") {
17416
+ const value = readLocalVault()[ref.name];
17417
+ if (value === undefined && ref.required !== false) {
17418
+ throw new CredentialReferenceError(`Missing required local vault credential "${ref.name}" for "${envKey}"`);
17419
+ }
17420
+ return value;
17421
+ }
17422
+ if (ref.required !== false) {
17423
+ throw new CredentialReferenceError(`Hosted credential "${ref.name}" for "${envKey}" cannot be resolved by the local runtime`);
17424
+ }
17425
+ return;
17426
+ }
17427
+ function resolveServerEnv(server) {
17428
+ const resolved = { ...server.env };
17429
+ const refs = normalizeCredentialRefs(server.credentialRefs);
17430
+ for (const [envKey, ref] of Object.entries(refs)) {
17431
+ const value = resolveCredentialRef(envKey, ref);
17432
+ if (value !== undefined)
17433
+ resolved[envKey] = value;
17434
+ }
17435
+ return resolved;
17436
+ }
17437
+ function credentialRefPlaceholders(refs) {
17438
+ const placeholders = {};
17439
+ for (const key of Object.keys(refs ?? {})) {
17440
+ placeholders[key] = REDACTED_CREDENTIAL_VALUE;
17441
+ }
17442
+ return placeholders;
17443
+ }
17444
+
17445
+ // src/lib/registry.ts
16914
17446
  function parseRow(row) {
16915
17447
  return {
16916
17448
  id: row.id,
@@ -16919,6 +17451,7 @@ function parseRow(row) {
16919
17451
  command: row.command,
16920
17452
  args: safeJsonParse(row.args, []),
16921
17453
  env: safeJsonParse(row.env, {}),
17454
+ credentialRefs: parseCredentialRefs(safeJsonParse(row.credential_refs, {})),
16922
17455
  transport: row.transport,
16923
17456
  url: row.url || null,
16924
17457
  source: row.source,
@@ -16986,9 +17519,9 @@ function addServer(opts) {
16986
17519
  if (!id) {
16987
17520
  throw new Error("Unable to generate a valid server ID");
16988
17521
  }
16989
- const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, transport, url, source)
16990
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
16991
- RETURNING *`).get(id, name, opts.description || null, command, JSON.stringify(opts.args || []), JSON.stringify(opts.env || {}), opts.transport || "stdio", opts.url || null, opts.source || "local");
17522
+ const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, credential_refs, transport, url, source)
17523
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
17524
+ RETURNING *`).get(id, name, opts.description || null, command, JSON.stringify(opts.args || []), JSON.stringify(normalizeLiteralEnv(opts.env)), JSON.stringify(normalizeCredentialRefs(opts.credentialRefs)), opts.transport || "stdio", opts.url || null, opts.source || "local");
16992
17525
  return parseRow(row);
16993
17526
  }
16994
17527
  function removeServer(id) {
@@ -17027,7 +17560,11 @@ function updateServer(id, updates) {
17027
17560
  }
17028
17561
  if (updates.env !== undefined) {
17029
17562
  sets.push("env = ?");
17030
- values.push(JSON.stringify(updates.env));
17563
+ values.push(JSON.stringify(normalizeLiteralEnv(updates.env)));
17564
+ }
17565
+ if (updates.credentialRefs !== undefined) {
17566
+ sets.push("credential_refs = ?");
17567
+ values.push(JSON.stringify(normalizeCredentialRefs(updates.credentialRefs)));
17031
17568
  }
17032
17569
  if (updates.transport !== undefined) {
17033
17570
  sets.push("transport = ?");
@@ -17061,7 +17598,7 @@ function setServerEnv(id, key, value) {
17061
17598
  if (!server)
17062
17599
  throw new Error(`Server "${id}" not found`);
17063
17600
  const env = { ...server.env, [key]: value };
17064
- db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
17601
+ db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(normalizeLiteralEnv(env)), id);
17065
17602
  }
17066
17603
  function unsetServerEnv(id, key) {
17067
17604
  const db2 = getDb();
@@ -17072,6 +17609,23 @@ function unsetServerEnv(id, key) {
17072
17609
  delete env[key];
17073
17610
  db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
17074
17611
  }
17612
+ function setServerCredentialRef(id, key, ref) {
17613
+ const db2 = getDb();
17614
+ const server = getServer(id);
17615
+ if (!server)
17616
+ throw new Error(`Server "${id}" not found`);
17617
+ const credentialRefs = normalizeCredentialRefs({ ...server.credentialRefs ?? {}, [key]: ref });
17618
+ db2.prepare("UPDATE servers SET credential_refs = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(credentialRefs), id);
17619
+ }
17620
+ function unsetServerCredentialRef(id, key) {
17621
+ const db2 = getDb();
17622
+ const server = getServer(id);
17623
+ if (!server)
17624
+ throw new Error(`Server "${id}" not found`);
17625
+ const credentialRefs = { ...server.credentialRefs ?? {} };
17626
+ delete credentialRefs[key];
17627
+ db2.prepare("UPDATE servers SET credential_refs = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(normalizeCredentialRefs(credentialRefs)), id);
17628
+ }
17075
17629
  function cacheTools(serverId, tools) {
17076
17630
  const db2 = getDb();
17077
17631
  const insert = db2.prepare("INSERT INTO tool_cache (server_id, name, description, input_schema) VALUES (?, ?, ?, ?)");
@@ -17111,6 +17665,7 @@ function cloneServer(id, newName) {
17111
17665
  command: server.command,
17112
17666
  args: server.args,
17113
17667
  env: server.env,
17668
+ credentialRefs: server.credentialRefs,
17114
17669
  transport: server.transport,
17115
17670
  url: server.url ?? undefined,
17116
17671
  source: server.source
@@ -25266,8 +25821,8 @@ var DESTRUCTIVE_COMMANDS = new Set([
25266
25821
  ]);
25267
25822
  var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
25268
25823
  var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
25269
- var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
25270
- var SECRET_VALUE_PATTERN = /^(sk_(?:live|test)_[A-Za-z0-9_]+|ghp_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|xox[baprs]-[A-Za-z0-9-]+|AIza[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/;
25824
+ var SECRET_KEY_PATTERN2 = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
25825
+ var SECRET_VALUE_PATTERN2 = /^(sk_(?:live|test)_[A-Za-z0-9_]+|ghp_[A-Za-z0-9_]+|github_pat_[A-Za-z0-9_]+|xox[baprs]-[A-Za-z0-9-]+|AIza[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/;
25271
25826
  var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
25272
25827
  function commandBase(command) {
25273
25828
  return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
@@ -25276,10 +25831,10 @@ function normalizeArgs(args) {
25276
25831
  return (args ?? []).map((arg) => String(arg));
25277
25832
  }
25278
25833
  function isSecretKey(key) {
25279
- return SECRET_KEY_PATTERN.test(key) || SECRET_FLAG_PATTERN.test(key);
25834
+ return SECRET_KEY_PATTERN2.test(key) || SECRET_FLAG_PATTERN.test(key);
25280
25835
  }
25281
25836
  function isSecretValue(value) {
25282
- return SECRET_VALUE_PATTERN.test(value.trim());
25837
+ return SECRET_VALUE_PATTERN2.test(value.trim());
25283
25838
  }
25284
25839
  function isSecretAssignment(arg) {
25285
25840
  const eqIdx = arg.indexOf("=");
@@ -25465,14 +26020,14 @@ async function connectToServer(entry, options = {}) {
25465
26020
  assertLocalCommandConsent({
25466
26021
  command: entry.command,
25467
26022
  args: entry.args,
25468
- env: entry.env,
26023
+ env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
25469
26024
  transport: entry.transport,
25470
26025
  operation: "launch"
25471
26026
  }, options.localCommandConsent);
25472
26027
  transport = new StdioClientTransport({
25473
26028
  command: entry.command,
25474
26029
  args: entry.args,
25475
- env: buildEnv(entry.env)
26030
+ env: buildEnv(resolveServerEnv(entry))
25476
26031
  });
25477
26032
  } else if (entry.transport === "sse") {
25478
26033
  transport = new SSEClientTransport(requireUrl(entry));
@@ -25612,7 +26167,7 @@ async function diagnoseServer(server, options = {}) {
25612
26167
  assertLocalCommandConsent({
25613
26168
  command: server.command,
25614
26169
  args: server.args,
25615
- env: server.env,
26170
+ env: { ...server.env, ...credentialRefPlaceholders(server.credentialRefs) },
25616
26171
  transport: server.transport,
25617
26172
  operation: "diagnose"
25618
26173
  }, options.localCommandConsent);
@@ -25641,12 +26196,29 @@ async function diagnoseServer(server, options = {}) {
25641
26196
  }
25642
26197
  }
25643
26198
  const missingEnv = Object.entries(server.env).filter(([, v]) => !v);
25644
- if (Object.keys(server.env).length === 0) {
25645
- checks3.push({ name: "env vars", pass: true, message: "no env vars required" });
25646
- } else if (missingEnv.length > 0) {
25647
- checks3.push({ name: "env vars", pass: false, message: `missing values for: ${missingEnv.map(([k]) => k).join(", ")}` });
26199
+ const credentialRefCount = Object.keys(server.credentialRefs ?? {}).length;
26200
+ let credentialError = null;
26201
+ try {
26202
+ resolveServerEnv(server);
26203
+ } catch (err) {
26204
+ credentialError = err.message;
26205
+ }
26206
+ if (Object.keys(server.env).length === 0 && credentialRefCount === 0) {
26207
+ checks3.push({ name: "env vars", pass: true, message: "no env vars or credential refs required" });
26208
+ } else if (missingEnv.length > 0 || credentialError) {
26209
+ const parts = [];
26210
+ if (missingEnv.length > 0)
26211
+ parts.push(`missing literal values for: ${missingEnv.map(([k]) => k).join(", ")}`);
26212
+ if (credentialError)
26213
+ parts.push(credentialError);
26214
+ checks3.push({ name: "env vars", pass: false, message: parts.join("; ") });
25648
26215
  } else {
25649
- checks3.push({ name: "env vars", pass: true, message: `${Object.keys(server.env).length} env var(s) set` });
26216
+ const literalCount = Object.keys(server.env).length;
26217
+ checks3.push({
26218
+ name: "env vars",
26219
+ pass: true,
26220
+ message: `${literalCount} literal env var(s), ${credentialRefCount} credential ref(s) available`
26221
+ });
25650
26222
  }
25651
26223
  if (server.transport !== "stdio" && server.url) {
25652
26224
  try {
@@ -26005,8 +26577,8 @@ init_provider_profile_seeds();
26005
26577
 
26006
26578
  // src/lib/install.ts
26007
26579
  import { execFileSync as execFileSync2 } from "child_process";
26008
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
26009
- import { join as join8 } from "path";
26580
+ import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
26581
+ import { join as join9 } from "path";
26010
26582
  import { homedir as homedir8 } from "os";
26011
26583
  function installToClaude(entry) {
26012
26584
  try {
@@ -26018,7 +26590,7 @@ function installToClaude(entry) {
26018
26590
  "--scope",
26019
26591
  "user"
26020
26592
  ];
26021
- for (const [k, v] of Object.entries(entry.env)) {
26593
+ for (const [k, v] of Object.entries(assertAgentInstallEnv(entry))) {
26022
26594
  args.push("--env", `${k}=${v}`);
26023
26595
  }
26024
26596
  args.push(entry.id, "--", entry.command, ...entry.args);
@@ -26030,9 +26602,9 @@ function installToClaude(entry) {
26030
26602
  }
26031
26603
  function installToCodex(entry) {
26032
26604
  try {
26033
- const configDir = join8(homedir8(), ".codex");
26034
- const configPath = join8(configDir, "config.toml");
26035
- if (!existsSync7(configDir)) {
26605
+ const configDir = join9(homedir8(), ".codex");
26606
+ const configPath = join9(configDir, "config.toml");
26607
+ if (!existsSync8(configDir)) {
26036
26608
  mkdirSync7(configDir, { recursive: true });
26037
26609
  }
26038
26610
  const block = `
@@ -26040,7 +26612,7 @@ function installToCodex(entry) {
26040
26612
  ` + `command = ${JSON.stringify(entry.command)}
26041
26613
  ` + `args = [${entry.args.map((a) => JSON.stringify(a)).join(", ")}]
26042
26614
  `;
26043
- const existing = existsSync7(configPath) ? readFileSync3(configPath, "utf-8") : "";
26615
+ const existing = existsSync8(configPath) ? readFileSync4(configPath, "utf-8") : "";
26044
26616
  if (existing.includes(`[mcp_servers.${entry.id}]`)) {
26045
26617
  return { agent: "codex", success: true };
26046
26618
  }
@@ -26052,21 +26624,22 @@ function installToCodex(entry) {
26052
26624
  }
26053
26625
  function installToGemini(entry) {
26054
26626
  try {
26055
- const configDir = join8(homedir8(), ".gemini");
26056
- const configPath = join8(configDir, "settings.json");
26057
- if (!existsSync7(configDir)) {
26627
+ const configDir = join9(homedir8(), ".gemini");
26628
+ const configPath = join9(configDir, "settings.json");
26629
+ if (!existsSync8(configDir)) {
26058
26630
  mkdirSync7(configDir, { recursive: true });
26059
26631
  }
26060
26632
  let settings = {};
26061
- if (existsSync7(configPath)) {
26062
- settings = JSON.parse(readFileSync3(configPath, "utf-8"));
26633
+ if (existsSync8(configPath)) {
26634
+ settings = JSON.parse(readFileSync4(configPath, "utf-8"));
26063
26635
  }
26064
26636
  if (!settings.mcpServers)
26065
26637
  settings.mcpServers = {};
26638
+ const env = assertAgentInstallEnv(entry);
26066
26639
  settings.mcpServers[entry.id] = {
26067
26640
  command: entry.command,
26068
26641
  args: entry.args,
26069
- ...Object.keys(entry.env).length > 0 ? { env: entry.env } : {}
26642
+ ...Object.keys(env).length > 0 ? { env } : {}
26070
26643
  };
26071
26644
  writeFileSync3(configPath, JSON.stringify(settings, null, 2), "utf-8");
26072
26645
  return { agent: "gemini", success: true };
@@ -26074,12 +26647,24 @@ function installToGemini(entry) {
26074
26647
  return { agent: "gemini", success: false, error: err.message };
26075
26648
  }
26076
26649
  }
26650
+ function assertAgentInstallEnv(entry) {
26651
+ const refs = entry.credentialRefs ?? {};
26652
+ if (Object.keys(refs).length > 0) {
26653
+ throw new CredentialReferenceError(`Server "${entry.id}" uses credential references; refusing to materialize secrets into local agent config files`);
26654
+ }
26655
+ for (const [key, value] of Object.entries(entry.env)) {
26656
+ if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
26657
+ throw new CredentialReferenceError(`Server "${entry.id}" has legacy raw secret-like env "${key}"; move it to a credential reference before installing to agents`);
26658
+ }
26659
+ }
26660
+ return entry.env;
26661
+ }
26077
26662
  function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
26078
26663
  try {
26079
26664
  assertLocalCommandConsent({
26080
26665
  command: entry.command,
26081
26666
  args: entry.args,
26082
- env: entry.env,
26667
+ env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
26083
26668
  transport: entry.transport,
26084
26669
  operation: "install"
26085
26670
  }, options.localCommandConsent);
@@ -26090,6 +26675,15 @@ function installToAgents(entry, targets = ["claude", "codex", "gemini"], options
26090
26675
  error: err.message
26091
26676
  }));
26092
26677
  }
26678
+ try {
26679
+ assertAgentInstallEnv(entry);
26680
+ } catch (err) {
26681
+ return targets.map((target) => ({
26682
+ agent: target,
26683
+ success: false,
26684
+ error: err.message
26685
+ }));
26686
+ }
26093
26687
  return targets.map((target) => {
26094
26688
  if (target === "claude")
26095
26689
  return installToClaude(entry);
@@ -26299,11 +26893,11 @@ function seedDefaultMachines() {
26299
26893
  // src/lib/fleet.ts
26300
26894
  init_config2();
26301
26895
  import { spawn as spawn2 } from "child_process";
26302
- import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
26303
- import { join as join9 } from "path";
26896
+ import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
26897
+ import { join as join10 } from "path";
26304
26898
  var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
26305
26899
  var NPM_REGISTRY_URL = "https://registry.npmjs.org";
26306
- var CATALOG_CACHE_PATH = join9(MCPS_DIR, "cache", "hasna-catalog.json");
26900
+ var CATALOG_CACHE_PATH = join10(MCPS_DIR, "cache", "hasna-catalog.json");
26307
26901
  var DEFAULT_CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
26308
26902
  var DEFAULT_REMOTE_TIMEOUT_MS = 180000;
26309
26903
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 2500;
@@ -26313,9 +26907,9 @@ function normalizeQueryList(values) {
26313
26907
  }
26314
26908
  function readCatalogCache(maxAgeMs) {
26315
26909
  try {
26316
- if (!existsSync8(CATALOG_CACHE_PATH))
26910
+ if (!existsSync9(CATALOG_CACHE_PATH))
26317
26911
  return null;
26318
- const parsed = JSON.parse(readFileSync4(CATALOG_CACHE_PATH, "utf-8"));
26912
+ const parsed = JSON.parse(readFileSync5(CATALOG_CACHE_PATH, "utf-8"));
26319
26913
  if (!parsed.cachedAt || !Array.isArray(parsed.entries))
26320
26914
  return null;
26321
26915
  if (Date.now() - parsed.cachedAt > maxAgeMs)
@@ -26327,7 +26921,7 @@ function readCatalogCache(maxAgeMs) {
26327
26921
  }
26328
26922
  function writeCatalogCache(entries) {
26329
26923
  try {
26330
- mkdirSync8(join9(MCPS_DIR, "cache"), { recursive: true });
26924
+ mkdirSync8(join10(MCPS_DIR, "cache"), { recursive: true });
26331
26925
  writeFileSync4(CATALOG_CACHE_PATH, JSON.stringify({ cachedAt: Date.now(), entries }, null, 2), "utf-8");
26332
26926
  } catch {}
26333
26927
  }
@@ -26998,22 +27592,22 @@ async function runFleetInstall(options = {}, dependencies = {}) {
26998
27592
  }));
26999
27593
  }
27000
27594
  // src/lib/version.ts
27001
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
27002
- import { dirname as dirname3, join as join10 } from "path";
27595
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
27596
+ import { dirname as dirname3, join as join11 } from "path";
27003
27597
  import { fileURLToPath } from "url";
27004
27598
  var FALLBACK_VERSION = "0.0.1";
27005
27599
  function readPackageVersion(moduleUrl, fallback = FALLBACK_VERSION) {
27006
27600
  const baseDir = dirname3(fileURLToPath(moduleUrl));
27007
27601
  const candidates = [
27008
- join10(baseDir, "..", "..", "package.json"),
27009
- join10(baseDir, "..", "package.json"),
27010
- join10(baseDir, "package.json")
27602
+ join11(baseDir, "..", "..", "package.json"),
27603
+ join11(baseDir, "..", "package.json"),
27604
+ join11(baseDir, "package.json")
27011
27605
  ];
27012
27606
  for (const candidate of candidates) {
27013
- if (!existsSync9(candidate))
27607
+ if (!existsSync10(candidate))
27014
27608
  continue;
27015
27609
  try {
27016
- const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
27610
+ const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
27017
27611
  if (pkg.version)
27018
27612
  return pkg.version;
27019
27613
  } catch {}
@@ -27029,19 +27623,27 @@ export {
27029
27623
  updateServer,
27030
27624
  updateMachine,
27031
27625
  unsetServerEnv,
27626
+ unsetServerCredentialRef,
27032
27627
  setServerEnv,
27628
+ setServerCredentialRef,
27033
27629
  seedDefaultProviderProfiles,
27034
27630
  seedDefaultMachines,
27035
27631
  searchRegistry,
27036
27632
  searchProviderProfiles,
27037
27633
  runFleetInstall,
27038
27634
  runFleetHealthCheck,
27635
+ resolveServerEnv,
27039
27636
  removeSource,
27040
27637
  removeServer,
27041
27638
  removeProviderProfile,
27042
27639
  removeMachine,
27043
27640
  refreshTools,
27641
+ redactServerCredentials,
27642
+ redactEnv,
27044
27643
  readPackageVersion,
27644
+ normalizeLiteralEnv,
27645
+ normalizeCredentialRefs,
27646
+ normalizeCredentialRef,
27045
27647
  listSources,
27046
27648
  listServers,
27047
27649
  listProviderProfiles,
@@ -27049,6 +27651,8 @@ export {
27049
27651
  listHasnaMcpCatalog,
27050
27652
  listAwesomeServers,
27051
27653
  listAllTools,
27654
+ isSecretLikeValue,
27655
+ isSecretLikeEnvKey,
27052
27656
  installToAgents,
27053
27657
  installProviderProfile,
27054
27658
  installFromRegistry,
@@ -27072,6 +27676,7 @@ export {
27072
27676
  disableServer,
27073
27677
  disableProviderProfile,
27074
27678
  diagnoseServer,
27679
+ credentialRefPlaceholders,
27075
27680
  connectToServer,
27076
27681
  closeDb,
27077
27682
  cloneServer,
@@ -27080,7 +27685,9 @@ export {
27080
27685
  addSource,
27081
27686
  addServer,
27082
27687
  addMachine,
27688
+ REDACTED_CREDENTIAL_VALUE,
27083
27689
  LocalCommandConsentError,
27084
27690
  DEFAULT_PROVIDER_PROFILE_SEEDS,
27085
- DEFAULT_MACHINE_SEEDS
27691
+ DEFAULT_MACHINE_SEEDS,
27692
+ CredentialReferenceError
27086
27693
  };