MCP is short for "Mod Coder Pack" and can be downloaded here.
Since Minecraft is written in Java (which does not compile directly into executables, but rather runs on a runtime engine), it is fairly easily decompilable. The problem with that is that it's functions, fields and classes are obfuscated, meaning their names are replaced by sequentially generated ids. Comments are also removed. This makes reading the code incredibly difficult.
The MCP tool gives us a mapping to and from obfuscated names and verbal, human understandable names. As well as enabling deobfuscation and reobfuscation of the game code, MCP also provides handy easy functionality to decompile, recompile, run locally, and a few other things.
In short, MCP is exactly intended to be a package to aid coders in creating Minecraft mods.
As you may know, Forge is a mod, more so an API, for writing big mods, making lots of mods compatible with each other, etc. It is generally the industry standard when it comes to playing modded Minecraft.
That being said, Forge is no more than an API for mod developers, and doesn't expose the full gamecode, nor does it run the original, "vanilla" engine for the game.
Forge is good if you're looking for a platform to make playstyle mods, or mods that require extensive compatability, IF you don't mind the game engine running slightly differently.
However, if you seek to understand game mechanics by reading game code and/or writing small mods to aid in debugging, testing, understanding, visualizing, etc., then MCP is the place to go.
As mentioned, one of the main things MCP does is deobfuscating the game's functions, fields, classes, etc. All classes are appropriately named in MCP, and a very large portion of fields and functions, and their parameters are also named. However, MCP is far from perfect.
While browsing the game code you will often run into function delarations that look like this:
public void func_180497_b(BlockPos p_180497_1_, Block p_180497_2_, int p_180497_3_, int p_180497_4_)
Here the types are named (including types introduced by classes in Minecraft, which are all named, as mentioned), but the name of the function and it's parameters are all still obfuscated. In fact, if you were to directly decompile Java code, this is generally what you see (except, again, class names are also obfuscated, which is probably the most important thing).
You may have noticed one thing not previously mentioned - variables. Oh yes, MCP doesn't deal with any variables. That's why reading the game code oft becomes a matter of finding matches to whatever you're trying to understand, and then seeing how they relate to each other and what happens in between. All functions will look pretty much like this:
IBlockState var5 = p_176345_4_;
int var6 = ((Integer)p_176345_4_.getValue(POWER)).intValue();
byte var7 = 0;
int var14 = this.func_176342_a(worldIn, p_176345_3_, var7);
this.canProvidePower = false;
int var8 = worldIn.func_175687_A(p_176345_2_);
this.canProvidePower = true;
if (var8 > 0 && var8 > var14 - 1)
var14 = var8;
} int var9 = 0;
Iterator var10 = EnumFacing.Plane.HORIZONTAL.iterator();
EnumFacing var11 = (EnumFacing)var10.next();
BlockPos var12 = p_176345_2_.offset(var11);
boolean var13 = var12.getX() != p_176345_3_.getX() || var12.getZ() != p_176345_3_.getZ();
var9 = this.func_176342_a(worldIn, var12, var9);
if (worldIn.getBlockState(var12).getBlock().isNormalCube() && !worldIn.getBlockState(p_176345_2_.offsetUp()).getBlock().isNormalCube())
if (var13 && p_176345_2_.getY() >= p_176345_3_.getY())
var9 = this.func_176342_a(worldIn, var12.offsetUp(), var9);
else if (!worldIn.getBlockState(var12).getBlock().isNormalCube() && var13 && p_176345_2_.getY() <= p_176345_3_.getY())
var9 = this.func_176342_a(worldIn, var12.offsetDown(), var9);
if (var9 > var14)
var14 = var9 - 1;
else if (var14 > 0)
var14 = 0;
if (var8 > var14 - 1)
var14 = var8;