Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions lib/depscore-tool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { envAsBoolean } from '@socketsecurity/lib/env/boolean'
import { getSocketDebug } from '@socketsecurity/lib/env/socket'
import { getSocketApiToken, getSocketApiUrl } from './env.ts'
import { httpRequest } from '@socketsecurity/lib/http-request/request'
Expand All @@ -12,6 +13,7 @@ import { registerAlertsTool } from './alerts-tool.ts'
import { registerOrganizationsTool } from './organizations-tool.ts'
import { registerPackageFilesTools } from './package-files-tool.ts'
import { registerThreatFeedTool } from './threat-feed-tool.ts'
import { withToolLogging } from './tool-logging.ts'

interface DepscorePackageInput {
ecosystem?: string | undefined
Expand All @@ -34,7 +36,7 @@ interface ToolOkResult {
// stack development; the default targets production. Both env vars
// resolved via fleet-canonical helpers.
const DEFAULT_SOCKET_API_URL =
getSocketDebug() === 'true'
envAsBoolean(getSocketDebug())
? 'http://localhost:8866/v0/purl?alerts=false&compact=false&fixable=false&licenseattrib=false&licensedetails=false'
: 'https://api.socket.dev/v0/purl?alerts=false&compact=false&fixable=false&licenseattrib=false&licensedetails=false'

Expand Down Expand Up @@ -97,7 +99,9 @@ export function buildPackageComponents(
// Build a configured McpServer with the depscore tool registered.
// Used for stdio (single instance) and HTTP (one per session).
export function createConfiguredServer(): McpServer {
const srv = new McpServer({ name: 'socket', version: VERSION })
const srv = withToolLogging(
new McpServer({ name: 'socket', version: VERSION }),
)
srv.registerTool(
'depscore',
{
Expand Down Expand Up @@ -153,6 +157,12 @@ export function formatScoreLine(jsonData: Record<string, unknown>): string {
return `${purl}: No score found`
}

// Read the boot-time static API key. Used by tool modules outside this file
// that share the same token-resolution chain as depscore.
export function getStaticApiKey(): string {
return staticApiKey
}

// Build the depscore handler — pulled out so the MCP registration is
// readable. The handler closes over the access token retrieval chain
// (request authInfo → env token).
Expand Down Expand Up @@ -276,12 +286,6 @@ export function parseSinglePackageBody(responseText: string): string[] {
return [formatScoreLine(jsonData)]
}

// Read the boot-time static API key. Used by tool modules outside this file
// that share the same token-resolution chain as depscore.
export function getStaticApiKey(): string {
return staticApiKey
}

// Set the static API key. Called once during boot from index.ts.
// Subsequent calls overwrite — only the most recent value is used.
export function setStaticApiKey(value: string): void {
Expand Down
22 changes: 19 additions & 3 deletions lib/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import os from 'node:os'
import path from 'node:path'
import pino from 'pino'

// Pino logger writing info-level to socket-mcp.log and errors to
import { envBool, envString } from './env.ts'

// Debug mode raises verbosity to `debug` and streams pretty logs to stderr
// so tool calls and error responses are visible live in the terminal.
// Enable with MCP_DEBUG=true (the `server-*:debug` scripts set it) or by
// setting LOG_LEVEL explicitly. stderr is safe in both stdio and HTTP modes
// — it is never the MCP protocol channel (stdout is).
const debug = envBool('MCP_DEBUG') || envString('LOG_LEVEL') === 'debug'
const level = envString('LOG_LEVEL') ?? (debug ? 'debug' : 'info')

// Pino logger writing the chosen level to socket-mcp.log and errors to
// socket-mcp-error.log under the platform tmp directory. Two file targets
// instead of one give grep-friendly error isolation without losing the
// info stream.
// info stream. A pretty stderr target surfaces errors to the terminal
// always, and the full debug stream when debug mode is on.
export const logger = pino({
level: 'info',
level,
transport: {
targets: [
{
Expand All @@ -22,6 +33,11 @@ export const logger = pino({
options: { destination: path.join(os.tmpdir(), 'socket-mcp.log') },
level: 'info',
},
{
target: 'pino-pretty',
options: { destination: 2 },
level: debug ? 'debug' : 'error',
},
],
},
})
56 changes: 56 additions & 0 deletions lib/tool-logging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'

import { logger } from './logger.ts'

interface ToolResult {
content?: unknown | undefined
isError?: boolean | undefined
}

// Wrap srv.registerTool so every tool logs its invocation args and its
// response. Request args + successful responses log at `debug` (enable with
// MCP_DEBUG=true); error responses and thrown errors always log at `error`
// so failures surface even in a normal run. Args carry no secrets — the
// access token rides on `extra.authInfo`, which is never logged here.
//
// Applied centrally in createConfiguredServer so all tools get the same
// treatment without each handler repeating the logging.
export function withToolLogging(srv: McpServer): McpServer {
const original = srv.registerTool.bind(srv) as McpServer['registerTool']
srv.registerTool = ((
name: string,
config: unknown,
handler: (...handlerArgs: unknown[]) => unknown,
) => {
const wrapped = async (...callArgs: unknown[]): Promise<unknown> => {
// 2-arg handlers (no input schema) get only `extra`; 3-arg form here
// means the first call arg is the tool's parsed input.
const args = callArgs.length > 1 ? callArgs[0] : undefined
logger.debug({ tool: name, args }, 'tool call')
try {
const result = (await handler(...callArgs)) as ToolResult
if (result?.isError) {
logger.error(
{ tool: name, response: result.content },
'tool call returned error',
)
} else {
logger.debug({ tool: name, response: result?.content }, 'tool result')
}
return result
} catch (e) {
logger.error(
{ tool: name, error: e instanceof Error ? e.message : String(e) },
'tool call threw',
)
throw e
}
}
return original(
name,
config as Parameters<McpServer['registerTool']>[1],
wrapped as Parameters<McpServer['registerTool']>[2],
)
}) as McpServer['registerTool']
return srv
}
74 changes: 38 additions & 36 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
{
"name": "@socketsecurity/mcp",
"version": "0.0.17",
"type": "module",
"main": "./index.js",
"description": "Socket MCP server for scanning dependencies",
"keywords": [],
"author": "Alexandros Kapravelos",
"repository": {
"type": "git",
"url": "https://github.com/SocketDev/socket-mcp"
},
"bin": {
"socket-mcp": "./index.js"
},
"engines": {
"node": ">=22.0.0",
"npm": ">=11.15.0"
},
"packageManager": "pnpm@11.3.0",
"files": [
"package.json",
"index.js",
"index.d.ts",
"index.d.ts.map",
"lib/**/*.js",
"lib/**/*.d.ts",
"mock-client/**/*.js",
"mock-client/**/*.d.ts*"
],
"type": "module",
"main": "./index.js",
"scripts": {
"prepare": "node scripts/install-git-hooks.mts",
"prepublishOnly": "npm run build",
Expand Down Expand Up @@ -40,30 +52,15 @@
"debug-sdk": "node --experimental-strip-types ./mock-client/stdio-client.ts",
"debug-http": "node --experimental-strip-types ./mock-client/http-client.ts",
"server-stdio": "SOCKET_API_TOKEN=${SOCKET_API_TOKEN:-${SOCKET_API_KEY}} node --experimental-strip-types ./index.ts",
"server-stdio:debug": "MCP_DEBUG=true SOCKET_API_TOKEN=${SOCKET_API_TOKEN:-${SOCKET_API_KEY}} node --experimental-strip-types ./index.ts",
"server-http": "MCP_HTTP_MODE=true SOCKET_API_TOKEN=${SOCKET_API_TOKEN:-${SOCKET_API_KEY}} node --experimental-strip-types ./index.ts",
"server-http:debug": "MCP_DEBUG=true MCP_HTTP_MODE=true SOCKET_API_TOKEN=${SOCKET_API_TOKEN:-${SOCKET_API_KEY}} node --experimental-strip-types ./index.ts",
"check:paths": "node scripts/check-paths.mts",
"security": "node scripts/security.mts",
"setup-security-tools": "node .claude/hooks/setup-security-tools/install.mts",
"lockstep": "node scripts/lockstep.mts",
"lockstep:emit-schema": "node scripts/lockstep-emit-schema.mts"
},
"keywords": [],
"files": [
"package.json",
"index.js",
"index.d.ts",
"index.d.ts.map",
"lib/**/*.js",
"lib/**/*.d.ts",
"mock-client/**/*.js",
"mock-client/**/*.d.ts*"
],
"author": "Alexandros Kapravelos",
"description": "Socket MCP server for scanning dependencies",
"repository": {
"type": "git",
"url": "https://github.com/SocketDev/socket-mcp"
},
"dependencies": {
"@anthropic-ai/mcpb": "^1.2.0",
"@modelcontextprotocol/sdk": "1.26.0",
Expand All @@ -75,6 +72,18 @@
"semver": "^7.7.4",
"zod": "3.25.76"
},
"devDependencies": {
"@sinclair/typebox": "catalog:",
"@types/node": "^24.12.3",
"@types/semver": "^7.7.1",
"@types/triple-beam": "^1.3.5",
"@typescript/native-preview": "7.0.0-dev.20260510.1",
"npm-run-all2": "^8.0.4",
"oxfmt": "0.48.0",
"oxlint": "1.63.0",
"taze": "catalog:",
"typescript": "~5.9.3"
},
"overrides": {
"@hono/node-server": "1.19.13",
"fast-uri": "3.1.2",
Expand All @@ -86,6 +95,11 @@
"zod": "3.25.76",
"zod-to-json-schema": "3.25.1"
},
"engines": {
"node": ">=22.0.0",
"npm": ">=11.15.0"
},
"packageManager": "pnpm@11.3.0",
"pnpm": {
"overrides": {
"@hono/node-server": "1.19.13",
Expand All @@ -99,18 +113,6 @@
"zod-to-json-schema": "3.25.1"
}
},
"devDependencies": {
"@sinclair/typebox": "catalog:",
"@types/node": "^24.12.3",
"@types/semver": "^7.7.1",
"@types/triple-beam": "^1.3.5",
"@typescript/native-preview": "7.0.0-dev.20260510.1",
"npm-run-all2": "^8.0.4",
"oxfmt": "0.48.0",
"oxlint": "1.63.0",
"taze": "catalog:",
"typescript": "~5.9.3"
},
"npm-run-all2": {
"nodeRun": true
}
Expand Down