@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/README.md +41 -0
- package/bin/index.js +842 -118
- package/bin/mcp.js +657 -67
- package/dist/index.d.ts +3 -2
- package/dist/index.js +678 -71
- package/dist/lib/credentials.d.ts +18 -0
- package/dist/lib/registry.d.ts +4 -2
- package/dist/mcp/index.js +657 -67
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -11985,7 +11985,7 @@ var init_provider_profile_seeds = __esm(() => {
|
|
|
11985
11985
|
provenance: {
|
|
11986
11986
|
source: "curated",
|
|
11987
11987
|
sourceUrl: "https://developers.notion.com/guides/mcp/build-mcp-client",
|
|
11988
|
-
verifiedAt: "2026-05-
|
|
11988
|
+
verifiedAt: "2026-05-11"
|
|
11989
11989
|
}
|
|
11990
11990
|
},
|
|
11991
11991
|
{
|
|
@@ -12017,7 +12017,384 @@ var init_provider_profile_seeds = __esm(() => {
|
|
|
12017
12017
|
provenance: {
|
|
12018
12018
|
source: "curated",
|
|
12019
12019
|
sourceUrl: "https://linear.app/docs/mcp",
|
|
12020
|
-
verifiedAt: "2026-05-
|
|
12020
|
+
verifiedAt: "2026-05-11"
|
|
12021
|
+
}
|
|
12022
|
+
},
|
|
12023
|
+
{
|
|
12024
|
+
id: "github",
|
|
12025
|
+
displayName: "GitHub",
|
|
12026
|
+
description: "Connect GitHub so agents can inspect repositories, manage issues and pull requests, analyze Actions runs, and work with project metadata.",
|
|
12027
|
+
endpoint: "https://api.githubcopilot.com/mcp/",
|
|
12028
|
+
transport: "streamable-http",
|
|
12029
|
+
authType: "oauth2",
|
|
12030
|
+
authMetadata: {
|
|
12031
|
+
oauthVersion: "2.0",
|
|
12032
|
+
pkce: true,
|
|
12033
|
+
dynamicClientRegistration: false,
|
|
12034
|
+
bearerToken: "optional",
|
|
12035
|
+
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."
|
|
12036
|
+
},
|
|
12037
|
+
scopes: ["repo", "read:org", "read:user", "user:email", "workflow", "notifications", "project"],
|
|
12038
|
+
tokenMode: "user",
|
|
12039
|
+
installFallback: {
|
|
12040
|
+
command: "docker",
|
|
12041
|
+
args: ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"],
|
|
12042
|
+
env: { GITHUB_PERSONAL_ACCESS_TOKEN: "GITHUB_PERSONAL_ACCESS_TOKEN" },
|
|
12043
|
+
packageName: "ghcr.io/github/github-mcp-server",
|
|
12044
|
+
url: "https://api.githubcopilot.com/mcp/"
|
|
12045
|
+
},
|
|
12046
|
+
docsUrl: "https://github.com/github/github-mcp-server",
|
|
12047
|
+
safety: {
|
|
12048
|
+
requiresApproval: true,
|
|
12049
|
+
sensitiveScopes: ["repo", "workflow", "project"],
|
|
12050
|
+
dataClasses: ["repositories", "issues", "pull_requests", "actions", "security_alerts", "organization_members"],
|
|
12051
|
+
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."
|
|
12052
|
+
},
|
|
12053
|
+
provenance: {
|
|
12054
|
+
source: "curated",
|
|
12055
|
+
sourceUrl: "https://github.com/github/github-mcp-server",
|
|
12056
|
+
repositoryUrl: "https://github.com/github/github-mcp-server",
|
|
12057
|
+
packageName: "ghcr.io/github/github-mcp-server",
|
|
12058
|
+
verifiedAt: "2026-05-11"
|
|
12059
|
+
}
|
|
12060
|
+
},
|
|
12061
|
+
{
|
|
12062
|
+
id: "slack",
|
|
12063
|
+
displayName: "Slack",
|
|
12064
|
+
description: "Connect Slack so agents can search workspace messages and files, inspect conversations, read user context, and draft or send approved messages.",
|
|
12065
|
+
endpoint: "https://mcp.slack.com/mcp",
|
|
12066
|
+
transport: "streamable-http",
|
|
12067
|
+
authType: "oauth2",
|
|
12068
|
+
authMetadata: {
|
|
12069
|
+
oauthVersion: "2.0",
|
|
12070
|
+
pkce: true,
|
|
12071
|
+
dynamicClientRegistration: false,
|
|
12072
|
+
bearerToken: "none",
|
|
12073
|
+
notes: "Slack's remote MCP endpoint uses Streamable HTTP, requires a registered Slack app identity, and does not support SSE or dynamic client registration."
|
|
12074
|
+
},
|
|
12075
|
+
scopes: [
|
|
12076
|
+
"search:read.public",
|
|
12077
|
+
"search:read.private",
|
|
12078
|
+
"search:read.mpim",
|
|
12079
|
+
"search:read.im",
|
|
12080
|
+
"search:read.files",
|
|
12081
|
+
"search:read.users",
|
|
12082
|
+
"channels:history",
|
|
12083
|
+
"groups:history",
|
|
12084
|
+
"mpim:history",
|
|
12085
|
+
"im:history",
|
|
12086
|
+
"chat:write",
|
|
12087
|
+
"canvases:read",
|
|
12088
|
+
"canvases:write",
|
|
12089
|
+
"users:read",
|
|
12090
|
+
"users:read.email"
|
|
12091
|
+
],
|
|
12092
|
+
tokenMode: "user",
|
|
12093
|
+
docsUrl: "https://docs.slack.dev/ai/slack-mcp-server/",
|
|
12094
|
+
safety: {
|
|
12095
|
+
requiresApproval: true,
|
|
12096
|
+
sensitiveScopes: ["chat:write", "canvases:write", "users:read.email"],
|
|
12097
|
+
dataClasses: ["messages", "files", "channels", "users", "canvases"],
|
|
12098
|
+
notes: "Slack data often includes confidential team communication. Search/read access should stay tenant-scoped and message/canvas writes should require explicit approval."
|
|
12099
|
+
},
|
|
12100
|
+
provenance: {
|
|
12101
|
+
source: "curated",
|
|
12102
|
+
sourceUrl: "https://docs.slack.dev/ai/slack-mcp-server/",
|
|
12103
|
+
verifiedAt: "2026-05-11"
|
|
12104
|
+
}
|
|
12105
|
+
},
|
|
12106
|
+
{
|
|
12107
|
+
id: "gmail",
|
|
12108
|
+
displayName: "Gmail",
|
|
12109
|
+
description: "Connect Gmail through the open Workspace MCP server for mail search, message retrieval, drafts, labels, filters, and approved send workflows.",
|
|
12110
|
+
transport: "stdio",
|
|
12111
|
+
authType: "oauth2",
|
|
12112
|
+
authMetadata: {
|
|
12113
|
+
oauthVersion: "2.1",
|
|
12114
|
+
pkce: true,
|
|
12115
|
+
dynamicClientRegistration: false,
|
|
12116
|
+
bearerToken: "optional",
|
|
12117
|
+
notes: "Workspace MCP is an independent open-source Google Workspace server. Configure your own Google OAuth client and storage backend for Gmail access."
|
|
12118
|
+
},
|
|
12119
|
+
scopes: [
|
|
12120
|
+
"https://www.googleapis.com/auth/gmail.readonly",
|
|
12121
|
+
"https://www.googleapis.com/auth/gmail.modify",
|
|
12122
|
+
"https://www.googleapis.com/auth/gmail.compose",
|
|
12123
|
+
"https://www.googleapis.com/auth/gmail.send"
|
|
12124
|
+
],
|
|
12125
|
+
tokenMode: "user",
|
|
12126
|
+
installFallback: {
|
|
12127
|
+
command: "uvx",
|
|
12128
|
+
args: ["workspace-mcp"],
|
|
12129
|
+
env: { WORKSPACE_MCP_PERMISSIONS: "gmail:send" },
|
|
12130
|
+
packageName: "workspace-mcp",
|
|
12131
|
+
url: "http://127.0.0.1:8000/mcp"
|
|
12132
|
+
},
|
|
12133
|
+
docsUrl: "https://workspacemcp.com/docs",
|
|
12134
|
+
safety: {
|
|
12135
|
+
requiresApproval: true,
|
|
12136
|
+
sensitiveScopes: [
|
|
12137
|
+
"https://www.googleapis.com/auth/gmail.modify",
|
|
12138
|
+
"https://www.googleapis.com/auth/gmail.compose",
|
|
12139
|
+
"https://www.googleapis.com/auth/gmail.send"
|
|
12140
|
+
],
|
|
12141
|
+
dataClasses: ["email_messages", "threads", "attachments", "labels", "contacts"],
|
|
12142
|
+
notes: "Email content and sending are high-impact actions. Prefer read-only permissions until a user intentionally enables drafts or sends."
|
|
12143
|
+
},
|
|
12144
|
+
provenance: {
|
|
12145
|
+
source: "github",
|
|
12146
|
+
sourceUrl: "https://workspacemcp.com/docs",
|
|
12147
|
+
repositoryUrl: "https://github.com/taylorwilsdon/google_workspace_mcp",
|
|
12148
|
+
packageName: "workspace-mcp",
|
|
12149
|
+
verifiedAt: "2026-05-11"
|
|
12150
|
+
}
|
|
12151
|
+
},
|
|
12152
|
+
{
|
|
12153
|
+
id: "google-drive",
|
|
12154
|
+
displayName: "Google Drive",
|
|
12155
|
+
description: "Connect Google Drive through the open Workspace MCP server for file search, folder navigation, content reads, and approved file operations.",
|
|
12156
|
+
transport: "stdio",
|
|
12157
|
+
authType: "oauth2",
|
|
12158
|
+
authMetadata: {
|
|
12159
|
+
oauthVersion: "2.1",
|
|
12160
|
+
pkce: true,
|
|
12161
|
+
dynamicClientRegistration: false,
|
|
12162
|
+
bearerToken: "optional",
|
|
12163
|
+
notes: "Workspace MCP is an independent open-source Google Workspace server. Configure your own Google OAuth client and storage backend for Drive access."
|
|
12164
|
+
},
|
|
12165
|
+
scopes: [
|
|
12166
|
+
"https://www.googleapis.com/auth/drive.readonly",
|
|
12167
|
+
"https://www.googleapis.com/auth/drive.file",
|
|
12168
|
+
"https://www.googleapis.com/auth/drive"
|
|
12169
|
+
],
|
|
12170
|
+
tokenMode: "user",
|
|
12171
|
+
installFallback: {
|
|
12172
|
+
command: "uvx",
|
|
12173
|
+
args: ["workspace-mcp"],
|
|
12174
|
+
env: { WORKSPACE_MCP_PERMISSIONS: "drive:readonly" },
|
|
12175
|
+
packageName: "workspace-mcp",
|
|
12176
|
+
url: "http://127.0.0.1:8000/mcp"
|
|
12177
|
+
},
|
|
12178
|
+
docsUrl: "https://workspacemcp.com/docs",
|
|
12179
|
+
safety: {
|
|
12180
|
+
requiresApproval: true,
|
|
12181
|
+
sensitiveScopes: [
|
|
12182
|
+
"https://www.googleapis.com/auth/drive.file",
|
|
12183
|
+
"https://www.googleapis.com/auth/drive"
|
|
12184
|
+
],
|
|
12185
|
+
dataClasses: ["drive_files", "folders", "documents", "spreadsheets", "attachments", "sharing_permissions"],
|
|
12186
|
+
notes: "Drive access can expose broad business documents. Keep tenant allowlists and approval-gate creation, movement, and permission changes."
|
|
12187
|
+
},
|
|
12188
|
+
provenance: {
|
|
12189
|
+
source: "github",
|
|
12190
|
+
sourceUrl: "https://workspacemcp.com/docs",
|
|
12191
|
+
repositoryUrl: "https://github.com/taylorwilsdon/google_workspace_mcp",
|
|
12192
|
+
packageName: "workspace-mcp",
|
|
12193
|
+
verifiedAt: "2026-05-11"
|
|
12194
|
+
}
|
|
12195
|
+
},
|
|
12196
|
+
{
|
|
12197
|
+
id: "google-calendar",
|
|
12198
|
+
displayName: "Google Calendar",
|
|
12199
|
+
description: "Connect Google Calendar through the open Workspace MCP server for calendar discovery, event reads, and approved scheduling changes.",
|
|
12200
|
+
transport: "stdio",
|
|
12201
|
+
authType: "oauth2",
|
|
12202
|
+
authMetadata: {
|
|
12203
|
+
oauthVersion: "2.1",
|
|
12204
|
+
pkce: true,
|
|
12205
|
+
dynamicClientRegistration: false,
|
|
12206
|
+
bearerToken: "optional",
|
|
12207
|
+
notes: "Workspace MCP is an independent open-source Google Workspace server. Configure your own Google OAuth client and storage backend for Calendar access."
|
|
12208
|
+
},
|
|
12209
|
+
scopes: [
|
|
12210
|
+
"https://www.googleapis.com/auth/calendar.readonly",
|
|
12211
|
+
"https://www.googleapis.com/auth/calendar.events",
|
|
12212
|
+
"https://www.googleapis.com/auth/calendar"
|
|
12213
|
+
],
|
|
12214
|
+
tokenMode: "user",
|
|
12215
|
+
installFallback: {
|
|
12216
|
+
command: "uvx",
|
|
12217
|
+
args: ["workspace-mcp"],
|
|
12218
|
+
env: { WORKSPACE_MCP_PERMISSIONS: "calendar:readonly" },
|
|
12219
|
+
packageName: "workspace-mcp",
|
|
12220
|
+
url: "http://127.0.0.1:8000/mcp"
|
|
12221
|
+
},
|
|
12222
|
+
docsUrl: "https://workspacemcp.com/docs",
|
|
12223
|
+
safety: {
|
|
12224
|
+
requiresApproval: true,
|
|
12225
|
+
sensitiveScopes: [
|
|
12226
|
+
"https://www.googleapis.com/auth/calendar.events",
|
|
12227
|
+
"https://www.googleapis.com/auth/calendar"
|
|
12228
|
+
],
|
|
12229
|
+
dataClasses: ["calendars", "events", "attendees", "attachments", "availability"],
|
|
12230
|
+
notes: "Calendar writes can invite people, reveal availability, and alter operational schedules. Gate creates, updates, and deletes with user approval."
|
|
12231
|
+
},
|
|
12232
|
+
provenance: {
|
|
12233
|
+
source: "github",
|
|
12234
|
+
sourceUrl: "https://workspacemcp.com/docs",
|
|
12235
|
+
repositoryUrl: "https://github.com/taylorwilsdon/google_workspace_mcp",
|
|
12236
|
+
packageName: "workspace-mcp",
|
|
12237
|
+
verifiedAt: "2026-05-11"
|
|
12238
|
+
}
|
|
12239
|
+
},
|
|
12240
|
+
{
|
|
12241
|
+
id: "stripe",
|
|
12242
|
+
displayName: "Stripe",
|
|
12243
|
+
description: "Connect Stripe so agents can inspect accounts, balances, customers, prices, subscriptions, invoices, disputes, and perform approved Stripe API operations.",
|
|
12244
|
+
endpoint: "https://mcp.stripe.com",
|
|
12245
|
+
transport: "streamable-http",
|
|
12246
|
+
authType: "oauth2",
|
|
12247
|
+
authMetadata: {
|
|
12248
|
+
oauthVersion: "2.0",
|
|
12249
|
+
pkce: true,
|
|
12250
|
+
dynamicClientRegistration: false,
|
|
12251
|
+
bearerToken: "optional",
|
|
12252
|
+
notes: "Stripe supports OAuth MCP sessions and restricted API keys as Authorization bearer tokens for clients that do not support OAuth."
|
|
12253
|
+
},
|
|
12254
|
+
tokenMode: "workspace",
|
|
12255
|
+
installFallback: {
|
|
12256
|
+
command: "npx",
|
|
12257
|
+
args: ["-y", "@stripe/mcp"],
|
|
12258
|
+
env: { STRIPE_SECRET_KEY: "STRIPE_SECRET_KEY" },
|
|
12259
|
+
packageName: "@stripe/mcp",
|
|
12260
|
+
url: "https://mcp.stripe.com"
|
|
12261
|
+
},
|
|
12262
|
+
docsUrl: "https://docs.stripe.com/mcp",
|
|
12263
|
+
safety: {
|
|
12264
|
+
requiresApproval: true,
|
|
12265
|
+
sensitiveScopes: ["restricted_api_key", "live_mode"],
|
|
12266
|
+
dataClasses: ["payments", "customers", "subscriptions", "invoices", "disputes", "account_balances"],
|
|
12267
|
+
notes: "Use restricted keys, separate sandbox/live mode, and require approval for any operation that creates, updates, refunds, cancels, or exposes customer data."
|
|
12268
|
+
},
|
|
12269
|
+
provenance: {
|
|
12270
|
+
source: "curated",
|
|
12271
|
+
sourceUrl: "https://docs.stripe.com/mcp",
|
|
12272
|
+
repositoryUrl: "https://github.com/stripe/ai",
|
|
12273
|
+
packageName: "@stripe/mcp",
|
|
12274
|
+
verifiedAt: "2026-05-11"
|
|
12275
|
+
}
|
|
12276
|
+
},
|
|
12277
|
+
{
|
|
12278
|
+
id: "cloudflare",
|
|
12279
|
+
displayName: "Cloudflare",
|
|
12280
|
+
description: "Connect Cloudflare's remote MCP server for account, zone, developer platform, logs, analytics, and approved infrastructure operations.",
|
|
12281
|
+
endpoint: "https://mcp.cloudflare.com/mcp",
|
|
12282
|
+
transport: "streamable-http",
|
|
12283
|
+
authType: "oauth2",
|
|
12284
|
+
authMetadata: {
|
|
12285
|
+
oauthVersion: "2.0",
|
|
12286
|
+
pkce: true,
|
|
12287
|
+
dynamicClientRegistration: false,
|
|
12288
|
+
bearerToken: "none",
|
|
12289
|
+
notes: "Cloudflare's remote MCP server redirects users through Cloudflare authorization and permission selection."
|
|
12290
|
+
},
|
|
12291
|
+
tokenMode: "workspace",
|
|
12292
|
+
docsUrl: "https://developers.cloudflare.com/agents/model-context-protocol/mcp-servers-for-cloudflare/",
|
|
12293
|
+
safety: {
|
|
12294
|
+
requiresApproval: true,
|
|
12295
|
+
sensitiveScopes: ["account_admin", "zone_write", "workers_write", "dns_write"],
|
|
12296
|
+
dataClasses: ["accounts", "zones", "dns_records", "workers", "logs", "analytics", "security_events"],
|
|
12297
|
+
notes: "Cloudflare tools can affect live infrastructure. Require account scoping, least-privilege permissions, and approval for write operations."
|
|
12298
|
+
},
|
|
12299
|
+
provenance: {
|
|
12300
|
+
source: "curated",
|
|
12301
|
+
sourceUrl: "https://github.com/cloudflare/mcp",
|
|
12302
|
+
repositoryUrl: "https://github.com/cloudflare/mcp",
|
|
12303
|
+
verifiedAt: "2026-05-11"
|
|
12304
|
+
}
|
|
12305
|
+
},
|
|
12306
|
+
{
|
|
12307
|
+
id: "postgres",
|
|
12308
|
+
displayName: "PostgreSQL",
|
|
12309
|
+
description: "Connect a PostgreSQL database through the reference read-only MCP server for schema inspection and SQL query analysis.",
|
|
12310
|
+
transport: "stdio",
|
|
12311
|
+
authType: "api_key",
|
|
12312
|
+
authMetadata: {
|
|
12313
|
+
bearerToken: "none",
|
|
12314
|
+
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."
|
|
12315
|
+
},
|
|
12316
|
+
tokenMode: "service",
|
|
12317
|
+
installFallback: {
|
|
12318
|
+
command: "npx",
|
|
12319
|
+
args: ["-y", "@modelcontextprotocol/server-postgres", "${POSTGRES_URL}"],
|
|
12320
|
+
packageName: "@modelcontextprotocol/server-postgres"
|
|
12321
|
+
},
|
|
12322
|
+
docsUrl: "https://www.npmjs.com/package/@modelcontextprotocol/server-postgres",
|
|
12323
|
+
safety: {
|
|
12324
|
+
readOnly: true,
|
|
12325
|
+
requiresApproval: false,
|
|
12326
|
+
sensitiveScopes: ["database_connection_url"],
|
|
12327
|
+
dataClasses: ["database_schema", "table_rows", "query_results"],
|
|
12328
|
+
notes: "Use read-only database users and network allowlists. Treat query results as sensitive even when the server enforces read-only transactions."
|
|
12329
|
+
},
|
|
12330
|
+
provenance: {
|
|
12331
|
+
source: "npm",
|
|
12332
|
+
sourceUrl: "https://www.npmjs.com/package/@modelcontextprotocol/server-postgres",
|
|
12333
|
+
repositoryUrl: "https://github.com/modelcontextprotocol/servers",
|
|
12334
|
+
packageName: "@modelcontextprotocol/server-postgres",
|
|
12335
|
+
verifiedAt: "2026-05-11"
|
|
12336
|
+
}
|
|
12337
|
+
},
|
|
12338
|
+
{
|
|
12339
|
+
id: "filesystem",
|
|
12340
|
+
displayName: "Filesystem",
|
|
12341
|
+
description: "Connect the reference filesystem MCP server for scoped local file and directory reads, writes, searches, metadata, and move operations.",
|
|
12342
|
+
transport: "stdio",
|
|
12343
|
+
authType: "none",
|
|
12344
|
+
authMetadata: {
|
|
12345
|
+
bearerToken: "none",
|
|
12346
|
+
notes: "Filesystem access is bounded by explicit root directories passed to the local stdio server."
|
|
12347
|
+
},
|
|
12348
|
+
tokenMode: "none",
|
|
12349
|
+
installFallback: {
|
|
12350
|
+
command: "npx",
|
|
12351
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "${workspaceFolder}"],
|
|
12352
|
+
packageName: "@modelcontextprotocol/server-filesystem"
|
|
12353
|
+
},
|
|
12354
|
+
docsUrl: "https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem",
|
|
12355
|
+
safety: {
|
|
12356
|
+
requiresApproval: true,
|
|
12357
|
+
destructiveTools: ["write_file", "delete_file", "move_file", "create_directory"],
|
|
12358
|
+
dataClasses: ["local_files", "source_code", "documents", "directory_metadata"],
|
|
12359
|
+
notes: "Always constrain roots to intended project directories and approval-gate write, delete, and move operations."
|
|
12360
|
+
},
|
|
12361
|
+
provenance: {
|
|
12362
|
+
source: "npm",
|
|
12363
|
+
sourceUrl: "https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem",
|
|
12364
|
+
repositoryUrl: "https://github.com/modelcontextprotocol/servers",
|
|
12365
|
+
packageName: "@modelcontextprotocol/server-filesystem",
|
|
12366
|
+
verifiedAt: "2026-05-11"
|
|
12367
|
+
}
|
|
12368
|
+
},
|
|
12369
|
+
{
|
|
12370
|
+
id: "browser",
|
|
12371
|
+
displayName: "Browser Automation",
|
|
12372
|
+
description: "Connect Playwright MCP so agents can navigate pages, inspect accessibility snapshots, fill forms, capture screenshots, and run browser automation.",
|
|
12373
|
+
transport: "stdio",
|
|
12374
|
+
authType: "none",
|
|
12375
|
+
authMetadata: {
|
|
12376
|
+
bearerToken: "none",
|
|
12377
|
+
notes: "Playwright MCP runs browser automation locally and may reuse browser profile state unless configured for isolation."
|
|
12378
|
+
},
|
|
12379
|
+
tokenMode: "none",
|
|
12380
|
+
installFallback: {
|
|
12381
|
+
command: "npx",
|
|
12382
|
+
args: ["-y", "@playwright/mcp"],
|
|
12383
|
+
packageName: "@playwright/mcp"
|
|
12384
|
+
},
|
|
12385
|
+
docsUrl: "https://playwright.dev/docs/getting-started-mcp",
|
|
12386
|
+
safety: {
|
|
12387
|
+
requiresApproval: true,
|
|
12388
|
+
destructiveTools: ["browser_click", "browser_type", "browser_file_upload", "browser_run_code"],
|
|
12389
|
+
dataClasses: ["web_pages", "forms", "cookies", "local_storage", "screenshots", "browser_profiles"],
|
|
12390
|
+
notes: "Browser automation can submit forms, alter accounts, and expose logged-in sessions. Prefer isolated profiles and require approval for side-effecting actions."
|
|
12391
|
+
},
|
|
12392
|
+
provenance: {
|
|
12393
|
+
source: "curated",
|
|
12394
|
+
sourceUrl: "https://playwright.dev/docs/getting-started-mcp",
|
|
12395
|
+
repositoryUrl: "https://github.com/microsoft/playwright-mcp",
|
|
12396
|
+
packageName: "@playwright/mcp",
|
|
12397
|
+
verifiedAt: "2026-05-11"
|
|
12021
12398
|
}
|
|
12022
12399
|
}
|
|
12023
12400
|
];
|
|
@@ -12040,6 +12417,7 @@ function getDb() {
|
|
|
12040
12417
|
command TEXT NOT NULL,
|
|
12041
12418
|
args TEXT NOT NULL DEFAULT '[]',
|
|
12042
12419
|
env TEXT NOT NULL DEFAULT '{}',
|
|
12420
|
+
credential_refs TEXT NOT NULL DEFAULT '{}',
|
|
12043
12421
|
transport TEXT NOT NULL DEFAULT 'stdio',
|
|
12044
12422
|
url TEXT,
|
|
12045
12423
|
source TEXT NOT NULL DEFAULT 'local',
|
|
@@ -12066,6 +12444,9 @@ function getDb() {
|
|
|
12066
12444
|
try {
|
|
12067
12445
|
db.exec("ALTER TABLE servers ADD COLUMN last_error TEXT");
|
|
12068
12446
|
} catch {}
|
|
12447
|
+
try {
|
|
12448
|
+
db.exec("ALTER TABLE servers ADD COLUMN credential_refs TEXT NOT NULL DEFAULT '{}'");
|
|
12449
|
+
} catch {}
|
|
12069
12450
|
db.exec(`
|
|
12070
12451
|
CREATE TABLE IF NOT EXISTS sources (
|
|
12071
12452
|
id TEXT PRIMARY KEY,
|
|
@@ -12136,22 +12517,19 @@ function getDb() {
|
|
|
12136
12517
|
db.exec("ALTER TABLE provider_profiles ADD COLUMN auth_metadata TEXT NOT NULL DEFAULT '{}'");
|
|
12137
12518
|
} catch {}
|
|
12138
12519
|
db.exec("CREATE INDEX IF NOT EXISTS idx_provider_profiles_enabled ON provider_profiles(enabled)");
|
|
12139
|
-
const
|
|
12140
|
-
|
|
12141
|
-
|
|
12142
|
-
|
|
12143
|
-
|
|
12144
|
-
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
});
|
|
12153
|
-
run();
|
|
12154
|
-
}
|
|
12520
|
+
const insertProviderProfile = db.prepare(`
|
|
12521
|
+
INSERT OR IGNORE INTO provider_profiles (
|
|
12522
|
+
id, display_name, description, endpoint, transport, fallback_endpoints,
|
|
12523
|
+
auth_type, auth_metadata, scopes, token_mode, install_fallback,
|
|
12524
|
+
docs_url, safety, provenance, enabled
|
|
12525
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
12526
|
+
`);
|
|
12527
|
+
const seedProviderProfiles = db.transaction(() => {
|
|
12528
|
+
for (const profile of DEFAULT_PROVIDER_PROFILE_SEEDS) {
|
|
12529
|
+
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);
|
|
12530
|
+
}
|
|
12531
|
+
});
|
|
12532
|
+
seedProviderProfiles();
|
|
12155
12533
|
db.exec(`
|
|
12156
12534
|
CREATE TABLE IF NOT EXISTS feedback (
|
|
12157
12535
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
@@ -19126,17 +19504,17 @@ __export(exports_sources, {
|
|
|
19126
19504
|
clearCache: () => clearCache,
|
|
19127
19505
|
addSource: () => addSource
|
|
19128
19506
|
});
|
|
19129
|
-
import { mkdirSync as mkdirSync6, existsSync as
|
|
19130
|
-
import { join as
|
|
19507
|
+
import { mkdirSync as mkdirSync6, existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync3, unlinkSync } from "fs";
|
|
19508
|
+
import { join as join8 } from "path";
|
|
19131
19509
|
function getCacheFile(sourceId) {
|
|
19132
|
-
return
|
|
19510
|
+
return join8(CACHE_DIR, `${sourceId}.json`);
|
|
19133
19511
|
}
|
|
19134
19512
|
function readCache(sourceId) {
|
|
19135
19513
|
try {
|
|
19136
19514
|
const file = getCacheFile(sourceId);
|
|
19137
|
-
if (!
|
|
19515
|
+
if (!existsSync7(file))
|
|
19138
19516
|
return null;
|
|
19139
|
-
const data = JSON.parse(
|
|
19517
|
+
const data = JSON.parse(readFileSync3(file, "utf-8"));
|
|
19140
19518
|
return data;
|
|
19141
19519
|
} catch {
|
|
19142
19520
|
return null;
|
|
@@ -19150,7 +19528,7 @@ function writeCache(sourceId, results) {
|
|
|
19150
19528
|
}
|
|
19151
19529
|
function clearCache(sourceId) {
|
|
19152
19530
|
try {
|
|
19153
|
-
if (!
|
|
19531
|
+
if (!existsSync7(CACHE_DIR))
|
|
19154
19532
|
return;
|
|
19155
19533
|
const files = readdirSync3(CACHE_DIR);
|
|
19156
19534
|
for (const file of files) {
|
|
@@ -19158,7 +19536,7 @@ function clearCache(sourceId) {
|
|
|
19158
19536
|
continue;
|
|
19159
19537
|
if (!sourceId || file.startsWith(`${sourceId}.`)) {
|
|
19160
19538
|
try {
|
|
19161
|
-
unlinkSync(
|
|
19539
|
+
unlinkSync(join8(CACHE_DIR, file));
|
|
19162
19540
|
} catch {}
|
|
19163
19541
|
}
|
|
19164
19542
|
}
|
|
@@ -19404,7 +19782,7 @@ var CACHE_DIR, DEFAULT_TTL_MS;
|
|
|
19404
19782
|
var init_sources = __esm(() => {
|
|
19405
19783
|
init_db();
|
|
19406
19784
|
init_config2();
|
|
19407
|
-
CACHE_DIR =
|
|
19785
|
+
CACHE_DIR = join8(MCPS_DIR, "cache");
|
|
19408
19786
|
DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
19409
19787
|
});
|
|
19410
19788
|
|
|
@@ -19412,7 +19790,7 @@ var init_sources = __esm(() => {
|
|
|
19412
19790
|
var require_package = __commonJS((exports, module) => {
|
|
19413
19791
|
module.exports = {
|
|
19414
19792
|
name: "@hasna/mcps",
|
|
19415
|
-
version: "0.0.
|
|
19793
|
+
version: "0.0.17",
|
|
19416
19794
|
description: "Meta-MCP registry & CLI \u2014 discover, manage, and proxy MCP servers",
|
|
19417
19795
|
type: "module",
|
|
19418
19796
|
repository: {
|
|
@@ -21142,10 +21520,164 @@ var {
|
|
|
21142
21520
|
import React10 from "react";
|
|
21143
21521
|
import { render } from "ink";
|
|
21144
21522
|
import chalk2 from "chalk";
|
|
21145
|
-
import { readFileSync as
|
|
21523
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
21146
21524
|
|
|
21147
21525
|
// src/lib/registry.ts
|
|
21148
21526
|
init_db();
|
|
21527
|
+
|
|
21528
|
+
// src/lib/credentials.ts
|
|
21529
|
+
init_config2();
|
|
21530
|
+
import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
|
|
21531
|
+
import { join as join7 } from "path";
|
|
21532
|
+
|
|
21533
|
+
class CredentialReferenceError extends Error {
|
|
21534
|
+
constructor(message) {
|
|
21535
|
+
super(message);
|
|
21536
|
+
this.name = "CredentialReferenceError";
|
|
21537
|
+
}
|
|
21538
|
+
}
|
|
21539
|
+
var SECRET_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
|
|
21540
|
+
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_-]+)$/;
|
|
21541
|
+
var REDACTED_CREDENTIAL_VALUE = "<redacted>";
|
|
21542
|
+
function normalizeKey(key) {
|
|
21543
|
+
return key.trim();
|
|
21544
|
+
}
|
|
21545
|
+
function isRecord(value) {
|
|
21546
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21547
|
+
}
|
|
21548
|
+
function isSecretLikeEnvKey(key) {
|
|
21549
|
+
return SECRET_KEY_PATTERN.test(key);
|
|
21550
|
+
}
|
|
21551
|
+
function isSecretLikeValue(value) {
|
|
21552
|
+
return SECRET_VALUE_PATTERN.test(value.trim());
|
|
21553
|
+
}
|
|
21554
|
+
function normalizeCredentialRef(ref) {
|
|
21555
|
+
const source = ref.source;
|
|
21556
|
+
if (source !== "env" && source !== "local-vault" && source !== "hosted") {
|
|
21557
|
+
throw new CredentialReferenceError(`Unsupported credential reference source: ${String(source)}`);
|
|
21558
|
+
}
|
|
21559
|
+
const name = ref.name?.trim();
|
|
21560
|
+
if (!name) {
|
|
21561
|
+
throw new CredentialReferenceError("Credential reference name is required");
|
|
21562
|
+
}
|
|
21563
|
+
return {
|
|
21564
|
+
source,
|
|
21565
|
+
name,
|
|
21566
|
+
required: ref.required !== false,
|
|
21567
|
+
...ref.description ? { description: ref.description } : {}
|
|
21568
|
+
};
|
|
21569
|
+
}
|
|
21570
|
+
function normalizeCredentialRefs(refs) {
|
|
21571
|
+
const normalized = {};
|
|
21572
|
+
for (const [rawKey, ref] of Object.entries(refs ?? {})) {
|
|
21573
|
+
const key = normalizeKey(rawKey);
|
|
21574
|
+
if (!key)
|
|
21575
|
+
throw new CredentialReferenceError("Credential reference env key is required");
|
|
21576
|
+
normalized[key] = normalizeCredentialRef(ref);
|
|
21577
|
+
}
|
|
21578
|
+
return normalized;
|
|
21579
|
+
}
|
|
21580
|
+
function parseCredentialRefs(value) {
|
|
21581
|
+
if (!isRecord(value))
|
|
21582
|
+
return {};
|
|
21583
|
+
const refs = {};
|
|
21584
|
+
for (const [key, ref] of Object.entries(value)) {
|
|
21585
|
+
if (!isRecord(ref))
|
|
21586
|
+
continue;
|
|
21587
|
+
const source = ref.source;
|
|
21588
|
+
const name = ref.name;
|
|
21589
|
+
if ((source === "env" || source === "local-vault" || source === "hosted") && typeof name === "string" && name.trim()) {
|
|
21590
|
+
refs[key] = normalizeCredentialRef({
|
|
21591
|
+
source,
|
|
21592
|
+
name,
|
|
21593
|
+
required: typeof ref.required === "boolean" ? ref.required : true,
|
|
21594
|
+
description: typeof ref.description === "string" ? ref.description : undefined
|
|
21595
|
+
});
|
|
21596
|
+
}
|
|
21597
|
+
}
|
|
21598
|
+
return refs;
|
|
21599
|
+
}
|
|
21600
|
+
function normalizeLiteralEnv(env) {
|
|
21601
|
+
const normalized = {};
|
|
21602
|
+
for (const [rawKey, rawValue] of Object.entries(env ?? {})) {
|
|
21603
|
+
const key = normalizeKey(rawKey);
|
|
21604
|
+
if (!key)
|
|
21605
|
+
continue;
|
|
21606
|
+
const value = String(rawValue);
|
|
21607
|
+
if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
|
|
21608
|
+
throw new CredentialReferenceError(`Refusing to store raw secret-like env value for "${key}". Use a credential reference instead.`);
|
|
21609
|
+
}
|
|
21610
|
+
normalized[key] = value;
|
|
21611
|
+
}
|
|
21612
|
+
return normalized;
|
|
21613
|
+
}
|
|
21614
|
+
function redactEnv(env) {
|
|
21615
|
+
const redacted = {};
|
|
21616
|
+
for (const [key, value] of Object.entries(env)) {
|
|
21617
|
+
redacted[key] = isSecretLikeEnvKey(key) || isSecretLikeValue(value) ? REDACTED_CREDENTIAL_VALUE : value;
|
|
21618
|
+
}
|
|
21619
|
+
return redacted;
|
|
21620
|
+
}
|
|
21621
|
+
function redactServerCredentials(server) {
|
|
21622
|
+
return {
|
|
21623
|
+
...server,
|
|
21624
|
+
env: redactEnv(server.env),
|
|
21625
|
+
credentialRefs: normalizeCredentialRefs(server.credentialRefs)
|
|
21626
|
+
};
|
|
21627
|
+
}
|
|
21628
|
+
function readLocalVault() {
|
|
21629
|
+
const path = process.env.HASNA_MCPS_CREDENTIAL_VAULT_PATH ?? join7(MCPS_DIR, "credentials.local.json");
|
|
21630
|
+
if (!existsSync6(path))
|
|
21631
|
+
return {};
|
|
21632
|
+
const parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
21633
|
+
if (!isRecord(parsed))
|
|
21634
|
+
return {};
|
|
21635
|
+
const values = {};
|
|
21636
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
21637
|
+
if (typeof value === "string")
|
|
21638
|
+
values[key] = value;
|
|
21639
|
+
}
|
|
21640
|
+
return values;
|
|
21641
|
+
}
|
|
21642
|
+
function resolveCredentialRef(envKey, ref) {
|
|
21643
|
+
if (ref.source === "env") {
|
|
21644
|
+
const value = process.env[ref.name];
|
|
21645
|
+
if (value === undefined && ref.required !== false) {
|
|
21646
|
+
throw new CredentialReferenceError(`Missing required environment credential "${ref.name}" for "${envKey}"`);
|
|
21647
|
+
}
|
|
21648
|
+
return value;
|
|
21649
|
+
}
|
|
21650
|
+
if (ref.source === "local-vault") {
|
|
21651
|
+
const value = readLocalVault()[ref.name];
|
|
21652
|
+
if (value === undefined && ref.required !== false) {
|
|
21653
|
+
throw new CredentialReferenceError(`Missing required local vault credential "${ref.name}" for "${envKey}"`);
|
|
21654
|
+
}
|
|
21655
|
+
return value;
|
|
21656
|
+
}
|
|
21657
|
+
if (ref.required !== false) {
|
|
21658
|
+
throw new CredentialReferenceError(`Hosted credential "${ref.name}" for "${envKey}" cannot be resolved by the local runtime`);
|
|
21659
|
+
}
|
|
21660
|
+
return;
|
|
21661
|
+
}
|
|
21662
|
+
function resolveServerEnv(server) {
|
|
21663
|
+
const resolved = { ...server.env };
|
|
21664
|
+
const refs = normalizeCredentialRefs(server.credentialRefs);
|
|
21665
|
+
for (const [envKey, ref] of Object.entries(refs)) {
|
|
21666
|
+
const value = resolveCredentialRef(envKey, ref);
|
|
21667
|
+
if (value !== undefined)
|
|
21668
|
+
resolved[envKey] = value;
|
|
21669
|
+
}
|
|
21670
|
+
return resolved;
|
|
21671
|
+
}
|
|
21672
|
+
function credentialRefPlaceholders(refs) {
|
|
21673
|
+
const placeholders = {};
|
|
21674
|
+
for (const key of Object.keys(refs ?? {})) {
|
|
21675
|
+
placeholders[key] = REDACTED_CREDENTIAL_VALUE;
|
|
21676
|
+
}
|
|
21677
|
+
return placeholders;
|
|
21678
|
+
}
|
|
21679
|
+
|
|
21680
|
+
// src/lib/registry.ts
|
|
21149
21681
|
function parseRow(row) {
|
|
21150
21682
|
return {
|
|
21151
21683
|
id: row.id,
|
|
@@ -21154,6 +21686,7 @@ function parseRow(row) {
|
|
|
21154
21686
|
command: row.command,
|
|
21155
21687
|
args: safeJsonParse(row.args, []),
|
|
21156
21688
|
env: safeJsonParse(row.env, {}),
|
|
21689
|
+
credentialRefs: parseCredentialRefs(safeJsonParse(row.credential_refs, {})),
|
|
21157
21690
|
transport: row.transport,
|
|
21158
21691
|
url: row.url || null,
|
|
21159
21692
|
source: row.source,
|
|
@@ -21221,9 +21754,9 @@ function addServer(opts) {
|
|
|
21221
21754
|
if (!id) {
|
|
21222
21755
|
throw new Error("Unable to generate a valid server ID");
|
|
21223
21756
|
}
|
|
21224
|
-
const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, transport, url, source)
|
|
21225
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21226
|
-
RETURNING *`).get(id, name, opts.description || null, command, JSON.stringify(opts.args || []), JSON.stringify(opts.env
|
|
21757
|
+
const row = db2.prepare(`INSERT INTO servers (id, name, description, command, args, env, credential_refs, transport, url, source)
|
|
21758
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
21759
|
+
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");
|
|
21227
21760
|
return parseRow(row);
|
|
21228
21761
|
}
|
|
21229
21762
|
function removeServer(id) {
|
|
@@ -21262,7 +21795,11 @@ function updateServer(id, updates) {
|
|
|
21262
21795
|
}
|
|
21263
21796
|
if (updates.env !== undefined) {
|
|
21264
21797
|
sets.push("env = ?");
|
|
21265
|
-
values.push(JSON.stringify(updates.env));
|
|
21798
|
+
values.push(JSON.stringify(normalizeLiteralEnv(updates.env)));
|
|
21799
|
+
}
|
|
21800
|
+
if (updates.credentialRefs !== undefined) {
|
|
21801
|
+
sets.push("credential_refs = ?");
|
|
21802
|
+
values.push(JSON.stringify(normalizeCredentialRefs(updates.credentialRefs)));
|
|
21266
21803
|
}
|
|
21267
21804
|
if (updates.transport !== undefined) {
|
|
21268
21805
|
sets.push("transport = ?");
|
|
@@ -21296,7 +21833,7 @@ function setServerEnv(id, key, value) {
|
|
|
21296
21833
|
if (!server)
|
|
21297
21834
|
throw new Error(`Server "${id}" not found`);
|
|
21298
21835
|
const env = { ...server.env, [key]: value };
|
|
21299
|
-
db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
|
|
21836
|
+
db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(normalizeLiteralEnv(env)), id);
|
|
21300
21837
|
}
|
|
21301
21838
|
function unsetServerEnv(id, key) {
|
|
21302
21839
|
const db2 = getDb();
|
|
@@ -21307,6 +21844,23 @@ function unsetServerEnv(id, key) {
|
|
|
21307
21844
|
delete env[key];
|
|
21308
21845
|
db2.prepare("UPDATE servers SET env = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(env), id);
|
|
21309
21846
|
}
|
|
21847
|
+
function setServerCredentialRef(id, key, ref) {
|
|
21848
|
+
const db2 = getDb();
|
|
21849
|
+
const server = getServer(id);
|
|
21850
|
+
if (!server)
|
|
21851
|
+
throw new Error(`Server "${id}" not found`);
|
|
21852
|
+
const credentialRefs = normalizeCredentialRefs({ ...server.credentialRefs ?? {}, [key]: ref });
|
|
21853
|
+
db2.prepare("UPDATE servers SET credential_refs = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(credentialRefs), id);
|
|
21854
|
+
}
|
|
21855
|
+
function unsetServerCredentialRef(id, key) {
|
|
21856
|
+
const db2 = getDb();
|
|
21857
|
+
const server = getServer(id);
|
|
21858
|
+
if (!server)
|
|
21859
|
+
throw new Error(`Server "${id}" not found`);
|
|
21860
|
+
const credentialRefs = { ...server.credentialRefs ?? {} };
|
|
21861
|
+
delete credentialRefs[key];
|
|
21862
|
+
db2.prepare("UPDATE servers SET credential_refs = ?, updated_at = datetime('now') WHERE id = ?").run(JSON.stringify(normalizeCredentialRefs(credentialRefs)), id);
|
|
21863
|
+
}
|
|
21310
21864
|
function cacheTools(serverId, tools) {
|
|
21311
21865
|
const db2 = getDb();
|
|
21312
21866
|
const insert = db2.prepare("INSERT INTO tool_cache (server_id, name, description, input_schema) VALUES (?, ?, ?, ?)");
|
|
@@ -21346,6 +21900,7 @@ function cloneServer(id, newName) {
|
|
|
21346
21900
|
command: server.command,
|
|
21347
21901
|
args: server.args,
|
|
21348
21902
|
env: server.env,
|
|
21903
|
+
credentialRefs: server.credentialRefs,
|
|
21349
21904
|
transport: server.transport,
|
|
21350
21905
|
url: server.url ?? undefined,
|
|
21351
21906
|
source: server.source
|
|
@@ -35615,8 +36170,8 @@ var DESTRUCTIVE_COMMANDS = new Set([
|
|
|
35615
36170
|
]);
|
|
35616
36171
|
var SHELL_EVAL_FLAGS = new Set(["-c", "/c", "-Command", "-command", "-EncodedCommand", "-encodedcommand"]);
|
|
35617
36172
|
var SECRET_FLAG_PATTERN = /(?:^|[-_])(api[-_]?key|token|secret|password|passwd|credential|auth|private[-_]?key)(?:$|[-_])/i;
|
|
35618
|
-
var
|
|
35619
|
-
var
|
|
36173
|
+
var SECRET_KEY_PATTERN2 = /(?:^|[_-])(api[_-]?key|token|secret|password|passwd|credential|auth|private[_-]?key)(?:$|[_-])/i;
|
|
36174
|
+
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_-]+)$/;
|
|
35620
36175
|
var SHELL_META_PATTERN = /[;&|`<>]|\$\(/;
|
|
35621
36176
|
function commandBase(command) {
|
|
35622
36177
|
return command.trim().split(/[\\/]/).pop()?.toLowerCase() || command.trim().toLowerCase();
|
|
@@ -35625,10 +36180,10 @@ function normalizeArgs(args) {
|
|
|
35625
36180
|
return (args ?? []).map((arg) => String(arg));
|
|
35626
36181
|
}
|
|
35627
36182
|
function isSecretKey(key) {
|
|
35628
|
-
return
|
|
36183
|
+
return SECRET_KEY_PATTERN2.test(key) || SECRET_FLAG_PATTERN.test(key);
|
|
35629
36184
|
}
|
|
35630
36185
|
function isSecretValue(value) {
|
|
35631
|
-
return
|
|
36186
|
+
return SECRET_VALUE_PATTERN2.test(value.trim());
|
|
35632
36187
|
}
|
|
35633
36188
|
function isSecretAssignment(arg) {
|
|
35634
36189
|
const eqIdx = arg.indexOf("=");
|
|
@@ -35815,14 +36370,14 @@ async function connectToServer(entry, options = {}) {
|
|
|
35815
36370
|
assertLocalCommandConsent({
|
|
35816
36371
|
command: entry.command,
|
|
35817
36372
|
args: entry.args,
|
|
35818
|
-
env: entry.env,
|
|
36373
|
+
env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
|
|
35819
36374
|
transport: entry.transport,
|
|
35820
36375
|
operation: "launch"
|
|
35821
36376
|
}, options.localCommandConsent);
|
|
35822
36377
|
transport = new StdioClientTransport({
|
|
35823
36378
|
command: entry.command,
|
|
35824
36379
|
args: entry.args,
|
|
35825
|
-
env: buildEnv(entry
|
|
36380
|
+
env: buildEnv(resolveServerEnv(entry))
|
|
35826
36381
|
});
|
|
35827
36382
|
} else if (entry.transport === "sse") {
|
|
35828
36383
|
transport = new SSEClientTransport(requireUrl(entry));
|
|
@@ -35967,7 +36522,7 @@ async function diagnoseServer(server, options = {}) {
|
|
|
35967
36522
|
assertLocalCommandConsent({
|
|
35968
36523
|
command: server.command,
|
|
35969
36524
|
args: server.args,
|
|
35970
|
-
env: server.env,
|
|
36525
|
+
env: { ...server.env, ...credentialRefPlaceholders(server.credentialRefs) },
|
|
35971
36526
|
transport: server.transport,
|
|
35972
36527
|
operation: "diagnose"
|
|
35973
36528
|
}, options.localCommandConsent);
|
|
@@ -35996,12 +36551,29 @@ async function diagnoseServer(server, options = {}) {
|
|
|
35996
36551
|
}
|
|
35997
36552
|
}
|
|
35998
36553
|
const missingEnv = Object.entries(server.env).filter(([, v]) => !v);
|
|
35999
|
-
|
|
36000
|
-
|
|
36001
|
-
|
|
36002
|
-
|
|
36554
|
+
const credentialRefCount = Object.keys(server.credentialRefs ?? {}).length;
|
|
36555
|
+
let credentialError = null;
|
|
36556
|
+
try {
|
|
36557
|
+
resolveServerEnv(server);
|
|
36558
|
+
} catch (err) {
|
|
36559
|
+
credentialError = err.message;
|
|
36560
|
+
}
|
|
36561
|
+
if (Object.keys(server.env).length === 0 && credentialRefCount === 0) {
|
|
36562
|
+
checks4.push({ name: "env vars", pass: true, message: "no env vars or credential refs required" });
|
|
36563
|
+
} else if (missingEnv.length > 0 || credentialError) {
|
|
36564
|
+
const parts = [];
|
|
36565
|
+
if (missingEnv.length > 0)
|
|
36566
|
+
parts.push(`missing literal values for: ${missingEnv.map(([k]) => k).join(", ")}`);
|
|
36567
|
+
if (credentialError)
|
|
36568
|
+
parts.push(credentialError);
|
|
36569
|
+
checks4.push({ name: "env vars", pass: false, message: parts.join("; ") });
|
|
36003
36570
|
} else {
|
|
36004
|
-
|
|
36571
|
+
const literalCount = Object.keys(server.env).length;
|
|
36572
|
+
checks4.push({
|
|
36573
|
+
name: "env vars",
|
|
36574
|
+
pass: true,
|
|
36575
|
+
message: `${literalCount} literal env var(s), ${credentialRefCount} credential ref(s) available`
|
|
36576
|
+
});
|
|
36005
36577
|
}
|
|
36006
36578
|
if (server.transport !== "stdio" && server.url) {
|
|
36007
36579
|
try {
|
|
@@ -36118,8 +36690,8 @@ init_sources();
|
|
|
36118
36690
|
|
|
36119
36691
|
// src/lib/install.ts
|
|
36120
36692
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
36121
|
-
import { existsSync as
|
|
36122
|
-
import { join as
|
|
36693
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync7 } from "fs";
|
|
36694
|
+
import { join as join9 } from "path";
|
|
36123
36695
|
import { homedir as homedir8 } from "os";
|
|
36124
36696
|
function installToClaude(entry) {
|
|
36125
36697
|
try {
|
|
@@ -36131,7 +36703,7 @@ function installToClaude(entry) {
|
|
|
36131
36703
|
"--scope",
|
|
36132
36704
|
"user"
|
|
36133
36705
|
];
|
|
36134
|
-
for (const [k, v] of Object.entries(entry
|
|
36706
|
+
for (const [k, v] of Object.entries(assertAgentInstallEnv(entry))) {
|
|
36135
36707
|
args.push("--env", `${k}=${v}`);
|
|
36136
36708
|
}
|
|
36137
36709
|
args.push(entry.id, "--", entry.command, ...entry.args);
|
|
@@ -36143,9 +36715,9 @@ function installToClaude(entry) {
|
|
|
36143
36715
|
}
|
|
36144
36716
|
function installToCodex(entry) {
|
|
36145
36717
|
try {
|
|
36146
|
-
const configDir =
|
|
36147
|
-
const configPath =
|
|
36148
|
-
if (!
|
|
36718
|
+
const configDir = join9(homedir8(), ".codex");
|
|
36719
|
+
const configPath = join9(configDir, "config.toml");
|
|
36720
|
+
if (!existsSync8(configDir)) {
|
|
36149
36721
|
mkdirSync7(configDir, { recursive: true });
|
|
36150
36722
|
}
|
|
36151
36723
|
const block = `
|
|
@@ -36153,7 +36725,7 @@ function installToCodex(entry) {
|
|
|
36153
36725
|
` + `command = ${JSON.stringify(entry.command)}
|
|
36154
36726
|
` + `args = [${entry.args.map((a) => JSON.stringify(a)).join(", ")}]
|
|
36155
36727
|
`;
|
|
36156
|
-
const existing =
|
|
36728
|
+
const existing = existsSync8(configPath) ? readFileSync4(configPath, "utf-8") : "";
|
|
36157
36729
|
if (existing.includes(`[mcp_servers.${entry.id}]`)) {
|
|
36158
36730
|
return { agent: "codex", success: true };
|
|
36159
36731
|
}
|
|
@@ -36165,21 +36737,22 @@ function installToCodex(entry) {
|
|
|
36165
36737
|
}
|
|
36166
36738
|
function installToGemini(entry) {
|
|
36167
36739
|
try {
|
|
36168
|
-
const configDir =
|
|
36169
|
-
const configPath =
|
|
36170
|
-
if (!
|
|
36740
|
+
const configDir = join9(homedir8(), ".gemini");
|
|
36741
|
+
const configPath = join9(configDir, "settings.json");
|
|
36742
|
+
if (!existsSync8(configDir)) {
|
|
36171
36743
|
mkdirSync7(configDir, { recursive: true });
|
|
36172
36744
|
}
|
|
36173
36745
|
let settings = {};
|
|
36174
|
-
if (
|
|
36175
|
-
settings = JSON.parse(
|
|
36746
|
+
if (existsSync8(configPath)) {
|
|
36747
|
+
settings = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
36176
36748
|
}
|
|
36177
36749
|
if (!settings.mcpServers)
|
|
36178
36750
|
settings.mcpServers = {};
|
|
36751
|
+
const env = assertAgentInstallEnv(entry);
|
|
36179
36752
|
settings.mcpServers[entry.id] = {
|
|
36180
36753
|
command: entry.command,
|
|
36181
36754
|
args: entry.args,
|
|
36182
|
-
...Object.keys(
|
|
36755
|
+
...Object.keys(env).length > 0 ? { env } : {}
|
|
36183
36756
|
};
|
|
36184
36757
|
writeFileSync3(configPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
36185
36758
|
return { agent: "gemini", success: true };
|
|
@@ -36187,12 +36760,24 @@ function installToGemini(entry) {
|
|
|
36187
36760
|
return { agent: "gemini", success: false, error: err.message };
|
|
36188
36761
|
}
|
|
36189
36762
|
}
|
|
36763
|
+
function assertAgentInstallEnv(entry) {
|
|
36764
|
+
const refs = entry.credentialRefs ?? {};
|
|
36765
|
+
if (Object.keys(refs).length > 0) {
|
|
36766
|
+
throw new CredentialReferenceError(`Server "${entry.id}" uses credential references; refusing to materialize secrets into local agent config files`);
|
|
36767
|
+
}
|
|
36768
|
+
for (const [key, value] of Object.entries(entry.env)) {
|
|
36769
|
+
if (isSecretLikeEnvKey(key) || isSecretLikeValue(value)) {
|
|
36770
|
+
throw new CredentialReferenceError(`Server "${entry.id}" has legacy raw secret-like env "${key}"; move it to a credential reference before installing to agents`);
|
|
36771
|
+
}
|
|
36772
|
+
}
|
|
36773
|
+
return entry.env;
|
|
36774
|
+
}
|
|
36190
36775
|
function installToAgents(entry, targets = ["claude", "codex", "gemini"], options = {}) {
|
|
36191
36776
|
try {
|
|
36192
36777
|
assertLocalCommandConsent({
|
|
36193
36778
|
command: entry.command,
|
|
36194
36779
|
args: entry.args,
|
|
36195
|
-
env: entry.env,
|
|
36780
|
+
env: { ...entry.env, ...credentialRefPlaceholders(entry.credentialRefs) },
|
|
36196
36781
|
transport: entry.transport,
|
|
36197
36782
|
operation: "install"
|
|
36198
36783
|
}, options.localCommandConsent);
|
|
@@ -36203,6 +36788,15 @@ function installToAgents(entry, targets = ["claude", "codex", "gemini"], options
|
|
|
36203
36788
|
error: err.message
|
|
36204
36789
|
}));
|
|
36205
36790
|
}
|
|
36791
|
+
try {
|
|
36792
|
+
assertAgentInstallEnv(entry);
|
|
36793
|
+
} catch (err) {
|
|
36794
|
+
return targets.map((target) => ({
|
|
36795
|
+
agent: target,
|
|
36796
|
+
success: false,
|
|
36797
|
+
error: err.message
|
|
36798
|
+
}));
|
|
36799
|
+
}
|
|
36206
36800
|
return targets.map((target) => {
|
|
36207
36801
|
if (target === "claude")
|
|
36208
36802
|
return installToClaude(entry);
|
|
@@ -36417,11 +37011,11 @@ function seedDefaultMachines() {
|
|
|
36417
37011
|
// src/lib/fleet.ts
|
|
36418
37012
|
init_config2();
|
|
36419
37013
|
import { spawn as spawn2 } from "child_process";
|
|
36420
|
-
import { existsSync as
|
|
36421
|
-
import { join as
|
|
37014
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
37015
|
+
import { join as join10 } from "path";
|
|
36422
37016
|
var NPM_SEARCH_URL = "https://registry.npmjs.org/-/v1/search";
|
|
36423
37017
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
36424
|
-
var CATALOG_CACHE_PATH =
|
|
37018
|
+
var CATALOG_CACHE_PATH = join10(MCPS_DIR, "cache", "hasna-catalog.json");
|
|
36425
37019
|
var DEFAULT_CATALOG_CACHE_TTL_MS = 60 * 60 * 1000;
|
|
36426
37020
|
var DEFAULT_REMOTE_TIMEOUT_MS = 180000;
|
|
36427
37021
|
var DEFAULT_HANDSHAKE_TIMEOUT_MS = 2500;
|
|
@@ -36431,9 +37025,9 @@ function normalizeQueryList(values) {
|
|
|
36431
37025
|
}
|
|
36432
37026
|
function readCatalogCache(maxAgeMs) {
|
|
36433
37027
|
try {
|
|
36434
|
-
if (!
|
|
37028
|
+
if (!existsSync9(CATALOG_CACHE_PATH))
|
|
36435
37029
|
return null;
|
|
36436
|
-
const parsed = JSON.parse(
|
|
37030
|
+
const parsed = JSON.parse(readFileSync5(CATALOG_CACHE_PATH, "utf-8"));
|
|
36437
37031
|
if (!parsed.cachedAt || !Array.isArray(parsed.entries))
|
|
36438
37032
|
return null;
|
|
36439
37033
|
if (Date.now() - parsed.cachedAt > maxAgeMs)
|
|
@@ -36445,7 +37039,7 @@ function readCatalogCache(maxAgeMs) {
|
|
|
36445
37039
|
}
|
|
36446
37040
|
function writeCatalogCache(entries) {
|
|
36447
37041
|
try {
|
|
36448
|
-
mkdirSync8(
|
|
37042
|
+
mkdirSync8(join10(MCPS_DIR, "cache"), { recursive: true });
|
|
36449
37043
|
writeFileSync4(CATALOG_CACHE_PATH, JSON.stringify({ cachedAt: Date.now(), entries }, null, 2), "utf-8");
|
|
36450
37044
|
} catch {}
|
|
36451
37045
|
}
|
|
@@ -38449,22 +39043,22 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
38449
39043
|
};
|
|
38450
39044
|
|
|
38451
39045
|
// src/lib/version.ts
|
|
38452
|
-
import { existsSync as
|
|
38453
|
-
import { dirname as dirname3, join as
|
|
39046
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
39047
|
+
import { dirname as dirname3, join as join11 } from "path";
|
|
38454
39048
|
import { fileURLToPath } from "url";
|
|
38455
39049
|
var FALLBACK_VERSION = "0.0.1";
|
|
38456
39050
|
function readPackageVersion(moduleUrl, fallback = FALLBACK_VERSION) {
|
|
38457
39051
|
const baseDir = dirname3(fileURLToPath(moduleUrl));
|
|
38458
39052
|
const candidates = [
|
|
38459
|
-
|
|
38460
|
-
|
|
38461
|
-
|
|
39053
|
+
join11(baseDir, "..", "..", "package.json"),
|
|
39054
|
+
join11(baseDir, "..", "package.json"),
|
|
39055
|
+
join11(baseDir, "package.json")
|
|
38462
39056
|
];
|
|
38463
39057
|
for (const candidate of candidates) {
|
|
38464
|
-
if (!
|
|
39058
|
+
if (!existsSync10(candidate))
|
|
38465
39059
|
continue;
|
|
38466
39060
|
try {
|
|
38467
|
-
const pkg = JSON.parse(
|
|
39061
|
+
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
38468
39062
|
if (pkg.version)
|
|
38469
39063
|
return pkg.version;
|
|
38470
39064
|
} catch {}
|
|
@@ -38497,6 +39091,9 @@ function localConsent(input) {
|
|
|
38497
39091
|
source: "mcp"
|
|
38498
39092
|
};
|
|
38499
39093
|
}
|
|
39094
|
+
function readCredentialRefs(input) {
|
|
39095
|
+
return normalizeCredentialRefs(input.credential_refs ?? input.credentialRefs);
|
|
39096
|
+
}
|
|
38500
39097
|
function buildMcpTools() {
|
|
38501
39098
|
const definitions = [
|
|
38502
39099
|
{
|
|
@@ -38522,6 +39119,12 @@ function buildMcpTools() {
|
|
|
38522
39119
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("Transport type"),
|
|
38523
39120
|
url: exports_external2.string().optional().describe("URL for remote transports"),
|
|
38524
39121
|
env: exports_external2.record(exports_external2.string()).optional().describe("Environment variables"),
|
|
39122
|
+
credential_refs: exports_external2.record(exports_external2.object({
|
|
39123
|
+
source: exports_external2.enum(["env", "local-vault", "hosted"]),
|
|
39124
|
+
name: exports_external2.string(),
|
|
39125
|
+
required: exports_external2.boolean().optional(),
|
|
39126
|
+
description: exports_external2.string().optional()
|
|
39127
|
+
})).optional().describe("Credential references by server env key"),
|
|
38525
39128
|
allow_local_stdio: exports_external2.boolean().optional().describe("Approve registering this local stdio command"),
|
|
38526
39129
|
allow_risky_command: exports_external2.boolean().optional().describe("Approve registering risky local command patterns")
|
|
38527
39130
|
},
|
|
@@ -38529,9 +39132,16 @@ function buildMcpTools() {
|
|
|
38529
39132
|
const command = String(input.command);
|
|
38530
39133
|
const args = Array.isArray(input.args) ? input.args.map(String) : [];
|
|
38531
39134
|
const env = isRecordOfStrings(input.env) ? input.env : {};
|
|
39135
|
+
const credentialRefs = readCredentialRefs(input);
|
|
38532
39136
|
const transport = input.transport;
|
|
38533
39137
|
try {
|
|
38534
|
-
assertLocalCommandConsent({
|
|
39138
|
+
assertLocalCommandConsent({
|
|
39139
|
+
command,
|
|
39140
|
+
args,
|
|
39141
|
+
env: { ...env, ...Object.fromEntries(Object.keys(credentialRefs).map((key) => [key, "<credential-ref>"])) },
|
|
39142
|
+
transport,
|
|
39143
|
+
operation: "register"
|
|
39144
|
+
}, localConsent(input));
|
|
38535
39145
|
return jsonContent(addServer({
|
|
38536
39146
|
command,
|
|
38537
39147
|
args,
|
|
@@ -38539,7 +39149,8 @@ function buildMcpTools() {
|
|
|
38539
39149
|
description: typeof input.description === "string" ? input.description : undefined,
|
|
38540
39150
|
transport,
|
|
38541
39151
|
url: typeof input.url === "string" ? input.url : undefined,
|
|
38542
|
-
env
|
|
39152
|
+
env,
|
|
39153
|
+
credentialRefs
|
|
38543
39154
|
}));
|
|
38544
39155
|
} catch (err) {
|
|
38545
39156
|
return errorContent(err.message);
|
|
@@ -38607,6 +39218,12 @@ function buildMcpTools() {
|
|
|
38607
39218
|
args: exports_external2.array(exports_external2.string()).optional().describe("New args list"),
|
|
38608
39219
|
transport: exports_external2.enum(["stdio", "sse", "streamable-http"]).optional().describe("New transport type"),
|
|
38609
39220
|
url: exports_external2.string().optional().describe("New URL for remote transports"),
|
|
39221
|
+
credential_refs: exports_external2.record(exports_external2.object({
|
|
39222
|
+
source: exports_external2.enum(["env", "local-vault", "hosted"]),
|
|
39223
|
+
name: exports_external2.string(),
|
|
39224
|
+
required: exports_external2.boolean().optional(),
|
|
39225
|
+
description: exports_external2.string().optional()
|
|
39226
|
+
})).optional().describe("Credential references by server env key"),
|
|
38610
39227
|
allow_local_stdio: exports_external2.boolean().optional().describe("Approve updating this local stdio command"),
|
|
38611
39228
|
allow_risky_command: exports_external2.boolean().optional().describe("Approve risky local command patterns")
|
|
38612
39229
|
},
|
|
@@ -38624,6 +39241,8 @@ function buildMcpTools() {
|
|
|
38624
39241
|
fields.command = input.command;
|
|
38625
39242
|
if (Array.isArray(input.args))
|
|
38626
39243
|
fields.args = input.args.map(String);
|
|
39244
|
+
if (input.credential_refs !== undefined || input.credentialRefs !== undefined)
|
|
39245
|
+
fields.credentialRefs = readCredentialRefs(input);
|
|
38627
39246
|
if (input.transport === "stdio" || input.transport === "sse" || input.transport === "streamable-http")
|
|
38628
39247
|
fields.transport = input.transport;
|
|
38629
39248
|
if (typeof input.url === "string")
|
|
@@ -38633,7 +39252,10 @@ function buildMcpTools() {
|
|
|
38633
39252
|
assertLocalCommandConsent({
|
|
38634
39253
|
command: fields.command ?? existing.command,
|
|
38635
39254
|
args: fields.args ?? existing.args,
|
|
38636
|
-
env:
|
|
39255
|
+
env: {
|
|
39256
|
+
...existing.env,
|
|
39257
|
+
...Object.fromEntries(Object.keys(fields.credentialRefs ?? existing.credentialRefs ?? {}).map((key) => [key, "<credential-ref>"]))
|
|
39258
|
+
},
|
|
38637
39259
|
transport: fields.transport ?? existing.transport,
|
|
38638
39260
|
operation: "register"
|
|
38639
39261
|
}, localConsent(input));
|
|
@@ -38688,7 +39310,7 @@ function buildMcpTools() {
|
|
|
38688
39310
|
},
|
|
38689
39311
|
{
|
|
38690
39312
|
name: "list_provider_profiles",
|
|
38691
|
-
description: "List curated provider profiles for hosted/common MCP integrations such as
|
|
39313
|
+
description: "List curated provider profiles for hosted/common MCP integrations such as GitHub, Slack, Google Workspace, Stripe, Cloudflare, Postgres, filesystem, and browser automation.",
|
|
38692
39314
|
paramsSchema: {
|
|
38693
39315
|
enabled_only: exports_external2.boolean().optional().describe("Only include enabled provider profiles")
|
|
38694
39316
|
},
|
|
@@ -38698,7 +39320,7 @@ function buildMcpTools() {
|
|
|
38698
39320
|
name: "search_provider_profiles",
|
|
38699
39321
|
description: "Search curated provider profiles separately from raw MCP registry/source search.",
|
|
38700
39322
|
paramsSchema: {
|
|
38701
|
-
query: exports_external2.string().describe("Search query such as '
|
|
39323
|
+
query: exports_external2.string().describe("Search query such as 'github', 'slack', 'postgres', or an endpoint URL"),
|
|
38702
39324
|
enabled_only: exports_external2.boolean().optional().describe("Only include enabled provider profiles")
|
|
38703
39325
|
},
|
|
38704
39326
|
run: ({ query, enabled_only }) => jsonContent(searchProviderProfiles(String(query), { enabledOnly: enabled_only === true }))
|
|
@@ -39197,32 +39819,32 @@ if (isDirectRun) {
|
|
|
39197
39819
|
}
|
|
39198
39820
|
|
|
39199
39821
|
// src/server/serve.ts
|
|
39200
|
-
import { existsSync as
|
|
39201
|
-
import { join as
|
|
39822
|
+
import { existsSync as existsSync11 } from "fs";
|
|
39823
|
+
import { join as join12, dirname as dirname4, extname, resolve, relative as relative2, sep } from "path";
|
|
39202
39824
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
39203
39825
|
init_sources();
|
|
39204
39826
|
init_db();
|
|
39205
39827
|
function redactServer(server) {
|
|
39206
|
-
return { ...server, env: {} };
|
|
39828
|
+
return { ...redactServerCredentials(server), env: {} };
|
|
39207
39829
|
}
|
|
39208
39830
|
function resolveDashboardDir() {
|
|
39209
39831
|
const candidates = [];
|
|
39210
39832
|
try {
|
|
39211
39833
|
const scriptDir = dirname4(fileURLToPath2(import.meta.url));
|
|
39212
|
-
candidates.push(
|
|
39213
|
-
candidates.push(
|
|
39834
|
+
candidates.push(join12(scriptDir, "..", "dashboard", "dist"));
|
|
39835
|
+
candidates.push(join12(scriptDir, "..", "..", "dashboard", "dist"));
|
|
39214
39836
|
} catch {}
|
|
39215
39837
|
if (process.argv[1]) {
|
|
39216
39838
|
const mainDir = dirname4(process.argv[1]);
|
|
39217
|
-
candidates.push(
|
|
39218
|
-
candidates.push(
|
|
39839
|
+
candidates.push(join12(mainDir, "..", "dashboard", "dist"));
|
|
39840
|
+
candidates.push(join12(mainDir, "..", "..", "dashboard", "dist"));
|
|
39219
39841
|
}
|
|
39220
|
-
candidates.push(
|
|
39842
|
+
candidates.push(join12(process.cwd(), "dashboard", "dist"));
|
|
39221
39843
|
for (const candidate of candidates) {
|
|
39222
|
-
if (
|
|
39844
|
+
if (existsSync11(candidate))
|
|
39223
39845
|
return candidate;
|
|
39224
39846
|
}
|
|
39225
|
-
return
|
|
39847
|
+
return join12(process.cwd(), "dashboard", "dist");
|
|
39226
39848
|
}
|
|
39227
39849
|
var MIME_TYPES = {
|
|
39228
39850
|
".html": "text/html; charset=utf-8",
|
|
@@ -39324,7 +39946,7 @@ function getAllServersWithToolCount() {
|
|
|
39324
39946
|
}));
|
|
39325
39947
|
}
|
|
39326
39948
|
function serveStaticFile(filePath) {
|
|
39327
|
-
if (!
|
|
39949
|
+
if (!existsSync11(filePath))
|
|
39328
39950
|
return null;
|
|
39329
39951
|
const ext = extname(filePath);
|
|
39330
39952
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -39357,7 +39979,7 @@ async function startServer(port, options) {
|
|
|
39357
39979
|
const host = options?.host ?? "127.0.0.1";
|
|
39358
39980
|
getDb();
|
|
39359
39981
|
const dashboardDir = resolveDashboardDir();
|
|
39360
|
-
const dashboardExists =
|
|
39982
|
+
const dashboardExists = existsSync11(dashboardDir);
|
|
39361
39983
|
if (!dashboardExists) {
|
|
39362
39984
|
console.error(`
|
|
39363
39985
|
Dashboard not found at: ${dashboardDir}`);
|
|
@@ -39416,8 +40038,16 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39416
40038
|
return json({ error: "Invalid 'args' format" }, 400, port);
|
|
39417
40039
|
}
|
|
39418
40040
|
const args = body.args || [];
|
|
40041
|
+
const credentialRefs = normalizeCredentialRefs(body.credential_refs ?? body.credentialRefs);
|
|
40042
|
+
const env = body.env && typeof body.env === "object" && !Array.isArray(body.env) ? body.env : {};
|
|
39419
40043
|
try {
|
|
39420
|
-
assertLocalCommandConsent({
|
|
40044
|
+
assertLocalCommandConsent({
|
|
40045
|
+
command,
|
|
40046
|
+
args,
|
|
40047
|
+
env: { ...env, ...Object.fromEntries(Object.keys(credentialRefs).map((key) => [key, "<credential-ref>"])) },
|
|
40048
|
+
transport,
|
|
40049
|
+
operation: "register"
|
|
40050
|
+
}, consentFromInput(body));
|
|
39421
40051
|
} catch (err) {
|
|
39422
40052
|
return json({ error: err.message }, 400, port);
|
|
39423
40053
|
}
|
|
@@ -39427,10 +40057,14 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39427
40057
|
args,
|
|
39428
40058
|
description: body.description,
|
|
39429
40059
|
transport,
|
|
39430
|
-
url: body.url
|
|
40060
|
+
url: body.url,
|
|
40061
|
+
env,
|
|
40062
|
+
credentialRefs
|
|
39431
40063
|
});
|
|
39432
40064
|
return json(entry, 200, port);
|
|
39433
40065
|
} catch (e) {
|
|
40066
|
+
if (e instanceof CredentialReferenceError)
|
|
40067
|
+
return json({ error: e.message }, 400, port);
|
|
39434
40068
|
return json({ error: e instanceof Error ? e.message : "Failed to add server" }, 500, port);
|
|
39435
40069
|
}
|
|
39436
40070
|
}
|
|
@@ -39509,6 +40143,11 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39509
40143
|
fields.description = body.description;
|
|
39510
40144
|
if (body.command !== undefined)
|
|
39511
40145
|
fields.command = body.command;
|
|
40146
|
+
if (body.env !== undefined)
|
|
40147
|
+
fields.env = body.env;
|
|
40148
|
+
if (body.credential_refs !== undefined || body.credentialRefs !== undefined) {
|
|
40149
|
+
fields.credentialRefs = normalizeCredentialRefs(body.credential_refs ?? body.credentialRefs);
|
|
40150
|
+
}
|
|
39512
40151
|
if (body.transport !== undefined)
|
|
39513
40152
|
fields.transport = body.transport;
|
|
39514
40153
|
if (body.url !== undefined)
|
|
@@ -39524,7 +40163,10 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39524
40163
|
assertLocalCommandConsent({
|
|
39525
40164
|
command: fields.command ?? existing.command,
|
|
39526
40165
|
args: fields.args ?? existing.args,
|
|
39527
|
-
env:
|
|
40166
|
+
env: {
|
|
40167
|
+
...fields.env ?? existing.env,
|
|
40168
|
+
...Object.fromEntries(Object.keys(fields.credentialRefs ?? existing.credentialRefs ?? {}).map((key) => [key, "<credential-ref>"]))
|
|
40169
|
+
},
|
|
39528
40170
|
transport: fields.transport ?? existing.transport,
|
|
39529
40171
|
operation: "register"
|
|
39530
40172
|
}, consentFromInput(body));
|
|
@@ -39560,6 +40202,8 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39560
40202
|
setServerEnv(id, body.key, body.value);
|
|
39561
40203
|
return json({ ok: true }, 200, port);
|
|
39562
40204
|
} catch (e) {
|
|
40205
|
+
if (e instanceof CredentialReferenceError)
|
|
40206
|
+
return json({ error: e.message }, 400, port);
|
|
39563
40207
|
return json({ error: e instanceof Error ? e.message : "Failed" }, 500, port);
|
|
39564
40208
|
}
|
|
39565
40209
|
}
|
|
@@ -39762,7 +40406,7 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
39762
40406
|
return res2;
|
|
39763
40407
|
}
|
|
39764
40408
|
}
|
|
39765
|
-
const indexPath =
|
|
40409
|
+
const indexPath = join12(dashboardDir, "index.html");
|
|
39766
40410
|
const res = serveStaticFile(indexPath);
|
|
39767
40411
|
if (res)
|
|
39768
40412
|
return res;
|
|
@@ -40916,6 +41560,30 @@ function assertCliLocalCommandConsent(input, opts, approved = false) {
|
|
|
40916
41560
|
assertLocalCommandConsent(input, consent);
|
|
40917
41561
|
return consent;
|
|
40918
41562
|
}
|
|
41563
|
+
function parseCredentialPairs(pairs, source) {
|
|
41564
|
+
const refs = {};
|
|
41565
|
+
for (const pair of pairs ?? []) {
|
|
41566
|
+
const eqIdx = pair.indexOf("=");
|
|
41567
|
+
if (eqIdx <= 0)
|
|
41568
|
+
throw new Error(`Credential reference must use KEY=NAME format: ${pair}`);
|
|
41569
|
+
const key = pair.slice(0, eqIdx).trim();
|
|
41570
|
+
const name = pair.slice(eqIdx + 1).trim();
|
|
41571
|
+
if (!key || !name)
|
|
41572
|
+
throw new Error(`Credential reference must use KEY=NAME format: ${pair}`);
|
|
41573
|
+
refs[key] = { source, name, required: true };
|
|
41574
|
+
}
|
|
41575
|
+
return refs;
|
|
41576
|
+
}
|
|
41577
|
+
function credentialRefsFromOptions(opts) {
|
|
41578
|
+
return {
|
|
41579
|
+
...parseCredentialPairs(opts.credentialEnv, "env"),
|
|
41580
|
+
...parseCredentialPairs(opts.credentialLocal, "local-vault"),
|
|
41581
|
+
...parseCredentialPairs(opts.credentialHosted, "hosted")
|
|
41582
|
+
};
|
|
41583
|
+
}
|
|
41584
|
+
function formatCredentialRef(ref) {
|
|
41585
|
+
return `${ref.source}:${ref.name}`;
|
|
41586
|
+
}
|
|
40919
41587
|
function printProviderProfile(profile) {
|
|
40920
41588
|
const status = profile.enabled ? chalk2.green("enabled") : chalk2.red("disabled");
|
|
40921
41589
|
console.log(` ${chalk2.bold(profile.displayName)} ${chalk2.dim(`[${profile.id}]`)} \u2014 ${chalk2.dim(profile.transport)} \u2014 ${status}`);
|
|
@@ -41200,7 +41868,7 @@ function detectSourceType(url2) {
|
|
|
41200
41868
|
return "mcp-registry";
|
|
41201
41869
|
return null;
|
|
41202
41870
|
}
|
|
41203
|
-
program2.command("add").passThroughOptions().argument("[command]", "Command to run the MCP server").argument("[args...]", "Arguments for the command").option("--name <name>", "Display name for the server").option("--description <desc>", "Description").option("--from-registry <id>", "Install from official registry by ID").option("--transport <type>", "Transport type: stdio, sse, streamable-http", "stdio").option("--url <url>", "URL for remote transports").option("--env <pairs...>", "Environment variables as KEY=VALUE pairs").option("--wizard", "Interactive setup wizard").option("--force", "Register even if duplicate command exists").option("--yes", "Approve registering local stdio commands").option("--allow-local-stdio", "Approve registering local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").description("Add a local MCP server").action(async (command, args, opts) => {
|
|
41871
|
+
program2.command("add").passThroughOptions().argument("[command]", "Command to run the MCP server").argument("[args...]", "Arguments for the command").option("--name <name>", "Display name for the server").option("--description <desc>", "Description").option("--from-registry <id>", "Install from official registry by ID").option("--transport <type>", "Transport type: stdio, sse, streamable-http", "stdio").option("--url <url>", "URL for remote transports").option("--env <pairs...>", "Environment variables as KEY=VALUE pairs").option("--credential-env <pairs...>", "Credential refs as KEY=ENV_VAR pairs").option("--credential-local <pairs...>", "Credential refs as KEY=LOCAL_VAULT_NAME pairs").option("--credential-hosted <pairs...>", "Credential refs as KEY=HOSTED_CREDENTIAL_ID pairs").option("--wizard", "Interactive setup wizard").option("--force", "Register even if duplicate command exists").option("--yes", "Approve registering local stdio commands").option("--allow-local-stdio", "Approve registering local stdio commands").option("--allow-risky-command", "Approve high-risk local command patterns").description("Add a local MCP server").action(async (command, args, opts) => {
|
|
41204
41872
|
try {
|
|
41205
41873
|
if (opts.fromRegistry) {
|
|
41206
41874
|
console.log(chalk2.dim(`Installing "${opts.fromRegistry}" from registry...`));
|
|
@@ -41277,7 +41945,8 @@ Server to add:`));
|
|
|
41277
41945
|
name: wizardName || undefined,
|
|
41278
41946
|
description: wizardDescription || undefined,
|
|
41279
41947
|
transport,
|
|
41280
|
-
env
|
|
41948
|
+
env,
|
|
41949
|
+
credentialRefs: credentialRefsFromOptions(opts)
|
|
41281
41950
|
});
|
|
41282
41951
|
console.log(chalk2.green(`Added: ${server2.name} [${server2.id}]`));
|
|
41283
41952
|
closeDb();
|
|
@@ -41307,10 +41976,11 @@ Server to add:`));
|
|
|
41307
41976
|
envMap[key] = rest.join("=");
|
|
41308
41977
|
}
|
|
41309
41978
|
}
|
|
41979
|
+
const credentialRefs = credentialRefsFromOptions(opts);
|
|
41310
41980
|
assertCliLocalCommandConsent({
|
|
41311
41981
|
command,
|
|
41312
41982
|
args,
|
|
41313
|
-
env: envMap,
|
|
41983
|
+
env: { ...envMap, ...Object.fromEntries(Object.keys(credentialRefs).map((key) => [key, "<credential-ref>"])) },
|
|
41314
41984
|
transport: opts.transport,
|
|
41315
41985
|
operation: "register"
|
|
41316
41986
|
}, opts);
|
|
@@ -41321,7 +41991,8 @@ Server to add:`));
|
|
|
41321
41991
|
args,
|
|
41322
41992
|
transport: opts.transport,
|
|
41323
41993
|
url: opts.url,
|
|
41324
|
-
env: envMap
|
|
41994
|
+
env: envMap,
|
|
41995
|
+
credentialRefs
|
|
41325
41996
|
});
|
|
41326
41997
|
console.log(chalk2.green(`Added server: ${server.name} [${server.id}]`));
|
|
41327
41998
|
console.log(chalk2.dim(` ${server.command} ${server.args.join(" ")}`));
|
|
@@ -41520,20 +42191,24 @@ program2.command("info").argument("<id>", "Server ID").description("Show server
|
|
|
41520
42191
|
closeDb();
|
|
41521
42192
|
process.exit(1);
|
|
41522
42193
|
}
|
|
41523
|
-
|
|
41524
|
-
console.log(
|
|
41525
|
-
console.log(`
|
|
41526
|
-
console.log(`
|
|
41527
|
-
console.log(`
|
|
41528
|
-
|
|
41529
|
-
|
|
41530
|
-
|
|
41531
|
-
|
|
41532
|
-
|
|
41533
|
-
|
|
41534
|
-
}
|
|
41535
|
-
|
|
41536
|
-
|
|
42194
|
+
const safeServer = redactServerCredentials(server);
|
|
42195
|
+
console.log(chalk2.bold(safeServer.name) + " " + chalk2.dim(`[${safeServer.id}]`));
|
|
42196
|
+
console.log(` Status: ${safeServer.enabled ? chalk2.green("enabled") : chalk2.red("disabled")}`);
|
|
42197
|
+
console.log(` Source: ${safeServer.source}`);
|
|
42198
|
+
console.log(` Transport: ${safeServer.transport}`);
|
|
42199
|
+
console.log(` Command: ${safeServer.command} ${safeServer.args.join(" ")}`);
|
|
42200
|
+
if (safeServer.url)
|
|
42201
|
+
console.log(` URL: ${safeServer.url}`);
|
|
42202
|
+
if (safeServer.description)
|
|
42203
|
+
console.log(` Desc: ${safeServer.description}`);
|
|
42204
|
+
if (Object.keys(safeServer.env).length > 0) {
|
|
42205
|
+
console.log(` Env: ${Object.entries(safeServer.env).map(([k, v]) => `${k}=${v}`).join(", ")}`);
|
|
42206
|
+
}
|
|
42207
|
+
if (Object.keys(safeServer.credentialRefs ?? {}).length > 0) {
|
|
42208
|
+
console.log(` Cred refs: ${Object.entries(safeServer.credentialRefs ?? {}).map(([k, ref]) => `${k}=${formatCredentialRef(ref)}`).join(", ")}`);
|
|
42209
|
+
}
|
|
42210
|
+
console.log(` Created: ${safeServer.created_at}`);
|
|
42211
|
+
console.log(` Updated: ${safeServer.updated_at}`);
|
|
41537
42212
|
const cached2 = getCachedTools(id);
|
|
41538
42213
|
if (cached2.length > 0) {
|
|
41539
42214
|
console.log(chalk2.bold(`
|
|
@@ -42173,7 +42848,7 @@ fleetCmd.command("install").argument("[machineIds...]", "Optional machine IDs to
|
|
|
42173
42848
|
}
|
|
42174
42849
|
});
|
|
42175
42850
|
program2.command("export").description("Export all servers and sources to a JSON file").option("--file <path>", "Output file path", `${process.env.HOME ?? "~"}/.hasna/mcps/export.json`).option("--stdout", "Write to stdout instead of a file").action((opts) => {
|
|
42176
|
-
const servers = listServers();
|
|
42851
|
+
const servers = listServers().map(redactServerCredentials);
|
|
42177
42852
|
const sources = listSources();
|
|
42178
42853
|
const payload = {
|
|
42179
42854
|
version: 1,
|
|
@@ -42193,7 +42868,7 @@ program2.command("export").description("Export all servers and sources to a JSON
|
|
|
42193
42868
|
program2.command("import").argument("<file>", "Path to the export JSON file").description("Import servers and sources from a JSON export file").option("--overwrite", "Overwrite existing entries with matching IDs").action((file, opts) => {
|
|
42194
42869
|
let payload;
|
|
42195
42870
|
try {
|
|
42196
|
-
payload = JSON.parse(
|
|
42871
|
+
payload = JSON.parse(readFileSync7(file, "utf-8"));
|
|
42197
42872
|
} catch (err) {
|
|
42198
42873
|
console.error(chalk2.red(`Failed to read file: ${err.message}`));
|
|
42199
42874
|
closeDb();
|
|
@@ -42215,7 +42890,23 @@ program2.command("import").argument("<file>", "Path to the export JSON file").de
|
|
|
42215
42890
|
serversSkipped++;
|
|
42216
42891
|
continue;
|
|
42217
42892
|
}
|
|
42218
|
-
|
|
42893
|
+
const literalEnv = normalizeLiteralEnv(s.env ?? {});
|
|
42894
|
+
const credentialRefs = normalizeCredentialRefs(s.credentialRefs ?? s.credential_refs ?? {});
|
|
42895
|
+
db2.run(`INSERT ${orReplace} INTO servers (id, name, description, command, args, env, credential_refs, transport, url, source, enabled, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)`, [
|
|
42896
|
+
s.id,
|
|
42897
|
+
s.name,
|
|
42898
|
+
s.description,
|
|
42899
|
+
s.command,
|
|
42900
|
+
JSON.stringify(s.args ?? []),
|
|
42901
|
+
JSON.stringify(literalEnv),
|
|
42902
|
+
JSON.stringify(credentialRefs),
|
|
42903
|
+
s.transport,
|
|
42904
|
+
s.url,
|
|
42905
|
+
s.source,
|
|
42906
|
+
s.enabled ? 1 : 0,
|
|
42907
|
+
s.created_at,
|
|
42908
|
+
s.updated_at
|
|
42909
|
+
]);
|
|
42219
42910
|
serversImported++;
|
|
42220
42911
|
}
|
|
42221
42912
|
let sourcesImported = 0;
|
|
@@ -42241,14 +42932,17 @@ envCmd.command("list").argument("<id>").description("List env vars for a server"
|
|
|
42241
42932
|
closeDb();
|
|
42242
42933
|
process.exit(1);
|
|
42243
42934
|
}
|
|
42244
|
-
const
|
|
42245
|
-
|
|
42246
|
-
|
|
42935
|
+
const envEntries = Object.entries(redactEnv(server.env));
|
|
42936
|
+
const refEntries = Object.entries(server.credentialRefs ?? {});
|
|
42937
|
+
if (envEntries.length === 0 && refEntries.length === 0) {
|
|
42938
|
+
console.log(chalk2.dim("No env vars or credential refs set."));
|
|
42247
42939
|
closeDb();
|
|
42248
42940
|
return;
|
|
42249
42941
|
}
|
|
42250
|
-
for (const [k, v] of
|
|
42942
|
+
for (const [k, v] of envEntries)
|
|
42251
42943
|
console.log(` ${chalk2.bold(k)}=${chalk2.dim(v)}`);
|
|
42944
|
+
for (const [k, ref] of refEntries)
|
|
42945
|
+
console.log(` ${chalk2.bold(k)}=${chalk2.dim(formatCredentialRef(ref))}`);
|
|
42252
42946
|
closeDb();
|
|
42253
42947
|
});
|
|
42254
42948
|
envCmd.command("set").argument("<id>").argument("<pair>", "KEY=VALUE").description("Set an env var").action((id, pair) => {
|
|
@@ -42270,6 +42964,36 @@ envCmd.command("set").argument("<id>").argument("<pair>", "KEY=VALUE").descripti
|
|
|
42270
42964
|
}
|
|
42271
42965
|
closeDb();
|
|
42272
42966
|
});
|
|
42967
|
+
envCmd.command("ref").argument("<id>").argument("<pair>", "KEY=NAME").description("Set a credential reference for a server env key").option("--source <source>", "Credential source: env, local-vault, hosted", "env").action((id, pair, opts) => {
|
|
42968
|
+
const source = opts.source;
|
|
42969
|
+
if (source !== "env" && source !== "local-vault" && source !== "hosted") {
|
|
42970
|
+
console.error(chalk2.red("Source must be one of: env, local-vault, hosted"));
|
|
42971
|
+
closeDb();
|
|
42972
|
+
process.exit(1);
|
|
42973
|
+
}
|
|
42974
|
+
try {
|
|
42975
|
+
const refs = parseCredentialPairs([pair], source);
|
|
42976
|
+
const [key, ref] = Object.entries(refs)[0];
|
|
42977
|
+
setServerCredentialRef(id, key, ref);
|
|
42978
|
+
console.log(chalk2.green(`Set credential ref ${key}=${formatCredentialRef(ref)} on ${id}`));
|
|
42979
|
+
} catch (err) {
|
|
42980
|
+
console.error(chalk2.red(err.message));
|
|
42981
|
+
closeDb();
|
|
42982
|
+
process.exit(1);
|
|
42983
|
+
}
|
|
42984
|
+
closeDb();
|
|
42985
|
+
});
|
|
42986
|
+
envCmd.command("unset-ref").argument("<id>").argument("<key>").description("Remove a credential reference").action((id, key) => {
|
|
42987
|
+
try {
|
|
42988
|
+
unsetServerCredentialRef(id, key);
|
|
42989
|
+
console.log(chalk2.green(`Unset credential ref ${key} on ${id}`));
|
|
42990
|
+
} catch (err) {
|
|
42991
|
+
console.error(chalk2.red(err.message));
|
|
42992
|
+
closeDb();
|
|
42993
|
+
process.exit(1);
|
|
42994
|
+
}
|
|
42995
|
+
closeDb();
|
|
42996
|
+
});
|
|
42273
42997
|
envCmd.command("unset").argument("<id>").argument("<key>").description("Remove an env var").action((id, key) => {
|
|
42274
42998
|
try {
|
|
42275
42999
|
unsetServerEnv(id, key);
|