Skip to main content
Glama
Hint-Services

Obsidian GitHub MCP

getCommitHistory

Retrieve and analyze commit history from a GitHub-hosted Obsidian vault to track changes in notes, ideas, and knowledge base evolution. Filter by author, include diffs, and paginate results for detailed insights.

Instructions

Track the evolution of your Obsidian vault knowledge base by retrieving commit history from GitHub (my-organization/obsidian-vault). See how your notes and ideas have developed over time with detailed diffs.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
authorNoFilter commits by author username
daysYesNumber of days to look back for commits
includeDiffsNoWhether to include actual file changes/diffs (default: true)
maxCommitsNoMaximum number of commits to return
pageNoPage number for pagination (0-indexed)

Implementation Reference

  • The async handler function implementing the getCommitHistory tool logic. Fetches recent commits using GitHub API, optionally retrieves detailed commit info with file diffs, formats output as structured markdown summary.
    async ({
      days,
      includeDiffs = true,
      author,
      maxCommits = 25,
      page = 0,
    }) => {
      // Calculate date range
      const since = new Date();
      since.setDate(since.getDate() - days);
      const sinceISO = since.toISOString();
    
      console.error("sinceISO", sinceISO);
    
      // Fetch commits list
      const commits = await this.handleRequest(async () => {
        return this.octokit.repos.listCommits({
          owner: this.config.owner,
          repo: this.config.repo,
          since: sinceISO,
          // author: author,
          page: page,
          per_page: maxCommits,
        });
      });
    
      if (commits.length === 0) {
        return {
          content: [
            {
              type: "text" as const,
              text: `No commits found in the last ${days} days since ${sinceISO}${
                author ? ` from author ${author}` : ""
              }.`,
            },
          ],
        };
      }
    
      let formattedOutput = `Found ${
        commits.length
      } commits in the last ${days} days${
        author ? ` from ${author}` : ""
      }:\n\n`;
    
      if (includeDiffs) {
        // Fetch detailed commit information with diffs
        const detailedCommits = await Promise.all(
          commits.slice(0, maxCommits).map(async (commit) => {
            const detailed = await this.handleRequest(async () => {
              return this.octokit.repos.getCommit({
                owner: this.config.owner,
                repo: this.config.repo,
                ref: commit.sha,
              });
            });
            return detailed;
          })
        );
    
        // Format results with diffs
        for (const commit of detailedCommits) {
          const shortSha = commit.sha.substring(0, 7);
          const commitUrl = `https://github.com/${this.config.owner}/${this.config.repo}/commit/${commit.sha}`;
    
          formattedOutput += `## Commit ${shortSha} (${commit.sha})\n`;
          formattedOutput += `**${commit.commit.message.split("\n")[0]}**\n`;
          formattedOutput += `Author: ${commit.commit.author?.name} <${commit.commit.author?.email}>\n`;
          formattedOutput += `Date: ${commit.commit.author?.date}\n`;
          formattedOutput += `URL: ${commitUrl}\n\n`;
    
          if (commit.files && commit.files.length > 0) {
            formattedOutput += `### Files Changed (${commit.files.length}):\n`;
            for (const file of commit.files) {
              const additions = file.additions || 0;
              const deletions = file.deletions || 0;
              formattedOutput += `- ${file.filename} (+${additions}, -${deletions})\n`;
            }
            formattedOutput += "\n### File Changes:\n\n";
    
            for (const file of commit.files) {
              formattedOutput += `#### ${file.filename}\n`;
              if (file.patch) {
                // Truncate large diffs for readability
                let patch = file.patch;
                const maxPatchLength = 8000; // Essay-length for note-taking
                if (patch.length > maxPatchLength) {
                  patch = `${patch.substring(
                    0,
                    maxPatchLength
                  )}\n\n... (diff truncated for readability) ...`;
                }
                formattedOutput += `\`\`\`diff\n${patch}\n\`\`\`\n\n`;
              } else {
                formattedOutput +=
                  "_No diff available (binary file or no changes to display)_\n\n";
              }
            }
          } else {
            formattedOutput += "No file changes detected.\n\n";
          }
          formattedOutput += "---\n\n";
        }
      } else {
        // Just show commit metadata without diffs
        for (const commit of commits) {
          const shortSha = commit.sha.substring(0, 7);
          const commitUrl = `https://github.com/${this.config.owner}/${this.config.repo}/commit/${commit.sha}`;
    
          formattedOutput += `## Commit ${shortSha}\n`;
          formattedOutput += `**${commit.commit.message.split("\n")[0]}**\n`;
          formattedOutput += `Author: ${commit.commit.author?.name} <${commit.commit.author?.email}>\n`;
          formattedOutput += `Date: ${commit.commit.author?.date}\n`;
          formattedOutput += `URL: ${commitUrl}\n\n`;
        }
      }
    
      return {
        content: [
          {
            type: "text" as const,
            text: formattedOutput,
          },
        ],
      };
    }
  • Zod input schema defining parameters for getCommitHistory: days (1-365), includeDiffs (bool), author (optional string), maxCommits (1-50), page (number).
    {
      days: z
        .number()
        .min(1)
        .max(365)
        .describe("Number of days to look back for commits"),
      includeDiffs: z
        .boolean()
        .optional()
        .default(true)
        .describe(
          "Whether to include actual file changes/diffs (default: true)"
        ),
      author: z
        .string()
        .optional()
        .describe("Filter commits by author username"),
      maxCommits: z
        .number()
        .min(1)
        .max(50)
        .optional()
        .default(25)
        .describe("Maximum number of commits to return"),
      page: z
        .number()
        .optional()
        .default(0)
        .describe("Page number for pagination (0-indexed)"),
    },
  • MCP server.tool registration for getCommitHistory, specifying name, description, input schema, execution hints, and handler reference.
    server.tool(
      "getCommitHistory",
      `Track the evolution of your Obsidian vault knowledge base by retrieving commit history from GitHub (${this.config.owner}/${this.config.repo}). See how your notes and ideas have developed over time with detailed diffs.`,
      {
        days: z
          .number()
          .min(1)
          .max(365)
          .describe("Number of days to look back for commits"),
        includeDiffs: z
          .boolean()
          .optional()
          .default(true)
          .describe(
            "Whether to include actual file changes/diffs (default: true)"
          ),
        author: z
          .string()
          .optional()
          .describe("Filter commits by author username"),
        maxCommits: z
          .number()
          .min(1)
          .max(50)
          .optional()
          .default(25)
          .describe("Maximum number of commits to return"),
        page: z
          .number()
          .optional()
          .default(0)
          .describe("Page number for pagination (0-indexed)"),
      },
      {
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: true,
      },
      async ({
        days,
        includeDiffs = true,
        author,
        maxCommits = 25,
        page = 0,
      }) => {
        // Calculate date range
        const since = new Date();
        since.setDate(since.getDate() - days);
        const sinceISO = since.toISOString();
    
        console.error("sinceISO", sinceISO);
    
        // Fetch commits list
        const commits = await this.handleRequest(async () => {
          return this.octokit.repos.listCommits({
            owner: this.config.owner,
            repo: this.config.repo,
            since: sinceISO,
            // author: author,
            page: page,
            per_page: maxCommits,
          });
        });
    
        if (commits.length === 0) {
          return {
            content: [
              {
                type: "text" as const,
                text: `No commits found in the last ${days} days since ${sinceISO}${
                  author ? ` from author ${author}` : ""
                }.`,
              },
            ],
          };
        }
    
        let formattedOutput = `Found ${
          commits.length
        } commits in the last ${days} days${
          author ? ` from ${author}` : ""
        }:\n\n`;
    
        if (includeDiffs) {
          // Fetch detailed commit information with diffs
          const detailedCommits = await Promise.all(
            commits.slice(0, maxCommits).map(async (commit) => {
              const detailed = await this.handleRequest(async () => {
                return this.octokit.repos.getCommit({
                  owner: this.config.owner,
                  repo: this.config.repo,
                  ref: commit.sha,
                });
              });
              return detailed;
            })
          );
    
          // Format results with diffs
          for (const commit of detailedCommits) {
            const shortSha = commit.sha.substring(0, 7);
            const commitUrl = `https://github.com/${this.config.owner}/${this.config.repo}/commit/${commit.sha}`;
    
            formattedOutput += `## Commit ${shortSha} (${commit.sha})\n`;
            formattedOutput += `**${commit.commit.message.split("\n")[0]}**\n`;
            formattedOutput += `Author: ${commit.commit.author?.name} <${commit.commit.author?.email}>\n`;
            formattedOutput += `Date: ${commit.commit.author?.date}\n`;
            formattedOutput += `URL: ${commitUrl}\n\n`;
    
            if (commit.files && commit.files.length > 0) {
              formattedOutput += `### Files Changed (${commit.files.length}):\n`;
              for (const file of commit.files) {
                const additions = file.additions || 0;
                const deletions = file.deletions || 0;
                formattedOutput += `- ${file.filename} (+${additions}, -${deletions})\n`;
              }
              formattedOutput += "\n### File Changes:\n\n";
    
              for (const file of commit.files) {
                formattedOutput += `#### ${file.filename}\n`;
                if (file.patch) {
                  // Truncate large diffs for readability
                  let patch = file.patch;
                  const maxPatchLength = 8000; // Essay-length for note-taking
                  if (patch.length > maxPatchLength) {
                    patch = `${patch.substring(
                      0,
                      maxPatchLength
                    )}\n\n... (diff truncated for readability) ...`;
                  }
                  formattedOutput += `\`\`\`diff\n${patch}\n\`\`\`\n\n`;
                } else {
                  formattedOutput +=
                    "_No diff available (binary file or no changes to display)_\n\n";
                }
              }
            } else {
              formattedOutput += "No file changes detected.\n\n";
            }
            formattedOutput += "---\n\n";
          }
        } else {
          // Just show commit metadata without diffs
          for (const commit of commits) {
            const shortSha = commit.sha.substring(0, 7);
            const commitUrl = `https://github.com/${this.config.owner}/${this.config.repo}/commit/${commit.sha}`;
    
            formattedOutput += `## Commit ${shortSha}\n`;
            formattedOutput += `**${commit.commit.message.split("\n")[0]}**\n`;
            formattedOutput += `Author: ${commit.commit.author?.name} <${commit.commit.author?.email}>\n`;
            formattedOutput += `Date: ${commit.commit.author?.date}\n`;
            formattedOutput += `URL: ${commitUrl}\n\n`;
          }
        }
    
        return {
          content: [
            {
              type: "text" as const,
              text: formattedOutput,
            },
          ],
        };
      }
    );
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions retrieving commit history with detailed diffs, which implies a read-only operation, but doesn't cover critical aspects like authentication requirements, rate limits, error handling, or the format of returned data (e.g., JSON structure, pagination details beyond the schema).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized with two sentences that are front-loaded with the main purpose. However, the second sentence ('See how your notes...') is somewhat promotional and could be more direct, slightly reducing efficiency.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of a 5-parameter tool with no annotations and no output schema, the description is incomplete. It lacks details on behavioral traits (e.g., auth, rate limits), output format, and usage guidelines, making it inadequate for an agent to fully understand how to invoke and interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all 5 parameters. The description adds minimal value beyond the schema by implying the tool is for tracking evolution over time, which contextualizes the 'days' parameter, but doesn't provide additional semantic details like examples or edge cases.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves commit history from a specific GitHub repository (my-organization/obsidian-vault) with detailed diffs, using specific verbs like 'retrieving' and 'track the evolution'. It distinguishes from siblings like getFileContents (file content) and searchIssues (issues), but doesn't explicitly contrast with searchFiles which might overlap in scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance on when to use this tool versus alternatives is provided. The description mentions tracking knowledge base evolution, which implies usage for historical analysis, but doesn't specify scenarios where other tools (e.g., searchFiles for current file content) would be more appropriate or any prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Hint-Services/obsidian-github-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server